]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Twitter lists compatible people tags api
authorShashi Gowda <connect2shashi@gmail.com>
Sun, 6 Mar 2011 18:06:38 +0000 (23:36 +0530)
committerShashi Gowda <connect2shashi@gmail.com>
Sun, 6 Mar 2011 19:06:06 +0000 (00:36 +0530)
15 files changed:
actions/apilist.php [new file with mode: 0644]
actions/apilistmember.php [new file with mode: 0644]
actions/apilistmembers.php [new file with mode: 0644]
actions/apilistmemberships.php [new file with mode: 0644]
actions/apilists.php [new file with mode: 0644]
actions/apilistsubscriber.php [new file with mode: 0644]
actions/apilistsubscribers.php [new file with mode: 0644]
actions/apilistsubscriptions.php [new file with mode: 0644]
actions/apitimelinelist.php [new file with mode: 0644]
lib/activityobject.php
lib/activityverb.php
lib/apiaction.php
lib/apilistusers.php [new file with mode: 0644]
lib/atomlistnoticefeed.php [new file with mode: 0644]
lib/router.php

diff --git a/actions/apilist.php b/actions/apilist.php
new file mode 100644 (file)
index 0000000..7a9ce70
--- /dev/null
@@ -0,0 +1,275 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show, update or delete a list.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @author    Shashi Gowda <connect2shashi@gmail.com>
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+class ApiListAction extends ApiBareAuthAction
+{
+    /**
+     * The list in question in the current request
+     */
+
+    var $list   = null;
+
+    /**
+     * Is this an update request?
+     */
+
+    var $update = false;
+
+    /**
+     * Is this a delete request?
+     */
+
+    var $delete = false;
+
+    /**
+     * Set the flags for handling the request. Show list if this is a GET
+     * request, update it if it is POST, delete list if method is DELETE
+     * or if method is POST and an argument _method is set to DELETE. Act
+     * like we don't know if the current user has no access to the list.
+     *
+     * Takes parameters:
+     *     - user: the user id or nickname
+     *     - id:   the id of the tag or the tag itself
+     *
+     * @return boolean success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->delete = ($_SERVER['REQUEST_METHOD'] == 'DELETE' ||
+                            ($this->trimmed('_method') == 'DELETE' &&
+                             $_SERVER['REQUEST_METHOD'] == 'POST'));
+
+        // update list if method is POST or PUT and $this->delete is not true
+        $this->update = (!$this->delete &&
+                         in_array($_SERVER['REQUEST_METHOD'], array('POST', 'PUT')));
+
+        $this->user = $this->getTargetUser($this->arg('user'));
+        $this->list = $this->getTargetList($this->arg('user'), $this->arg('id'));
+
+        if (empty($this->list)) {
+            $this->clientError(_('Not found'), 404, $this->format);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @return boolean success flag
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if($this->delete) {
+            $this->handleDelete();
+            return true;
+        }
+
+        if($this->update) {
+            $this->handlePut();
+            return true;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($this->list);
+            break;
+        case 'json':
+            $this->showSingleJsonList($this->list);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * require authentication if it is a write action or user is ambiguous
+     *
+     */
+
+    function requiresAuth()
+    {
+        return parent::requiresAuth() ||
+            $this->create || $this->delete;
+    }
+
+    /**
+     * Update a list
+     *
+     * @return boolean success
+     */
+
+    function handlePut()
+    {
+        if($this->auth_user->id != $this->list->tagger) {
+            $this->clientError(
+                _('You can not update lists that don\'t belong to you.'),
+                401,
+                $this->format
+            );
+        }
+
+        $new_list = clone($this->list);
+        $new_list->tag = common_canonical_tag($this->arg('name'));
+        $new_list->description = common_canonical_tag($this->arg('description'));
+        $new_list->private = ($this->arg('mode') === 'private') ? true : false;
+
+        $result = $new_list->update($this->list);
+
+        if(!$result) {
+            $this->clientError(
+                _('An error occured.'),
+                503,
+                $this->format
+            );
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($new_list);
+            break;
+        case 'json':
+            $this->showSingleJsonList($new_list);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Delete a list
+     *
+     * @return boolean success
+     */
+
+    function handleDelete()
+    {
+        if($this->auth_user->id != $this->list->tagger) {
+            $this->clientError(
+                _('You can not delete lists that don\'t belong to you.'),
+                401,
+                $this->format
+            );
+        }
+
+        $record = clone($this->list);
+        $this->list->delete();
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($record);
+            break;
+        case 'json':
+            $this->showSingleJsonList($record);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Indicate that this resource is not read-only.
+     *
+     * @return boolean is_read-only=false
+     */
+
+    function isReadOnly($args)
+    {
+        return false;
+    }
+
+    /**
+     * When was the list (people tag) last updated?
+     *
+     * @return String time_last_modified
+     */
+
+    function lastModified()
+    {
+        if(!empty($this->list)) {
+            return strtotime($this->list->modified);
+        }
+        return null;
+    }
+
+    /**
+     * An entity tag for this list
+     *
+     * Returns an Etag based on the action name, language, user ID and
+     * timestamps of the first and last list the user has joined
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->list)) {
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->list->created),
+                      strtotime($this->list->modified))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apilistmember.php b/actions/apilistmember.php
new file mode 100644 (file)
index 0000000..18d6ea3
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * API method to check if a user belongs to a list.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @author    Shashi Gowda <connect2shashi@gmail.com>
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Action handler for Twitter list_memeber methods
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Shashi Gowda <connect2shashi@gmail.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ * @see      ApiBareAuthAction
+ */
+
+class ApiListMemberAction extends ApiBareAuthAction
+{
+    /**
+     * Set the flags for handling the request. Show the profile if this
+     * is a GET request AND the profile is a member of the list, add a member
+     * if it is a POST, remove the profile from the list if method is DELETE
+     * or if method is POST and an argument _method is set to DELETE. Act
+     * like we don't know if the current user has no access to the list.
+     *
+     * Takes parameters:
+     *     - user: the user id or nickname
+     *     - list_id: the id of the tag or the tag itself
+     *     - id: the id of the member being looked for/added/removed
+     *
+     * @return boolean success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+        $this->list = $this->getTargetList($this->arg('user'), $this->arg('list_id'));
+
+        if (empty($this->list)) {
+            $this->clientError(_('Not found'), 404, $this->format);
+            return false;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user'), 404, $this->format);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @return boolean success flag
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $arr = array('tagger' => $this->list->tagger,
+                      'tag' => $this->list->tag,
+                      'tagged' => $this->user->id);
+        $ptag = Profile_tag::pkeyGet($arr);
+
+        if(empty($ptag)) {
+            $this->clientError(
+                _('The specified user is not a member of this list'),
+                400,
+                $this->format
+            );
+        }
+
+        $user = $this->twitterUserArray($this->user->getProfile(), true);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showTwitterXmlUser($user, 'user', true);
+            break;
+        case 'json':
+            $this->showSingleJsonUser($user);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+        return true;
+    }
+}
diff --git a/actions/apilistmembers.php b/actions/apilistmembers.php
new file mode 100644 (file)
index 0000000..c6e92fa
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * List/add/remove list members.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @author    Shashi Gowda <connect2shashi@gmail.com>
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apilistusers.php';
+
+class ApiListMembersAction extends ApiListUsersAction
+{
+    /**
+     * Add a user to a list (tag someone)
+     *
+     * @return boolean success
+     */
+
+    function handlePost()
+    {
+        if($this->auth_user->id != $this->list->tagger) {
+            $this->clientError(
+                _('You aren\'t allowed to add members to this list'),
+                401,
+                $this->format
+            );
+            return false;
+        }
+
+        if($this->user === false) {
+            $this->clientError(
+                _('You must specify a member'),
+                400,
+                $this->format
+            );
+            return false;
+        }
+
+        $result = Profile_tag::setTag($this->auth_user->id,
+                        $this->user->id, $this->list->tag);
+
+        if(empty($result)) {
+            $this->clientError(
+                _('An error occured.'),
+                500,
+                $this->format
+            );
+            return false;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($this->list);
+            break;
+        case 'json':
+            $this->showSingleJsonList($this->list);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            return false;
+            break;
+        }
+    }
+
+    /**
+     * Remove a user from a list (untag someone)
+     *
+     * @return boolean success
+     */
+
+    function handleDelete()
+    {
+        if($this->auth_user->id != $this->list->tagger) {
+            $this->clientError(
+                _('You aren\'t allowed to remove members from this list'),
+                401,
+                $this->format
+            );
+            return false;
+        }
+
+        if($this->user === false) {
+            $this->clientError(
+                _('You must specify a member'),
+                400,
+                $this->format
+            );
+            return false;
+        }
+
+        $args = array('tagger' => $this->auth_user->id,
+                      'tagged' => $this->user->id,
+                      'tag' => $this->list->tag);
+        $ptag = Profile_tag::pkeyGet($args);
+
+        if(empty($ptag)) {
+            $this->clientError(
+                _('The user you are trying to remove from the list is not a member'),
+                400,
+                $this->format
+            );
+            return false;
+        }
+
+        $result = $ptag->delete();
+
+        if(empty($result)) {
+            $this->clientError(
+                _('An error occured.'),
+                500,
+                $this->format
+            );
+            return false;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($this->list);
+            break;
+        case 'json':
+            $this->showSingleJsonList($this->list);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            return false;
+            break;
+        }
+        return true;
+    }
+
+    /**
+     * List the members of a list (people tagged)
+     */
+
+    function getUsers()
+    {
+        $fn = array($this->list, 'getTagged');
+        list($this->users, $this->next_cursor, $this->prev_cursor) =
+            Profile_list::getAtCursor($fn, array(), $this->cursor, 20);
+    }
+}
diff --git a/actions/apilistmemberships.php b/actions/apilistmemberships.php
new file mode 100644 (file)
index 0000000..635f970
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Get a list of lists a user belongs to. (people tags for a user)
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @author    Shashi Gowda <connect2shashi@gmail.com>
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Action handler for API method to list lists a user belongs to.
+ * (people tags for a user)
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Shashi Gowda <connect2shashi@gmail.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ * @see      ApiBareAuthAction
+ */
+
+class ApiListMembershipsAction extends ApiBareAuthAction
+{
+    var $lists = array();
+    var $cursor = -1;
+    var $next_cursor = 0;
+    var $prev_cursor = 0;
+
+    /**
+     * Prepare for running the action
+     * Take arguments for running:s
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->cursor = (int) $this->arg('cursor', -1);
+        $this->user = $this->getTargetUser($this->arg('user'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user.'), 404, $this->format);
+            return;
+        }
+
+        $this->getLists();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the lists
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlLists($this->lists, $this->next_cursor, $this->prev_cursor);
+            break;
+        case 'json':
+            $this->showJsonLists($this->lists, $this->next_cursor, $this->prev_cursor);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                400,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    function getLists()
+    {
+        $profile = $this->user->getProfile();
+        $fn = array($profile, 'getOtherTags');
+
+        # 20 lists
+        list($this->lists, $this->next_cursor, $this->prev_cursor) =
+                Profile_list::getAtCursor($fn, array($this->auth_user), $this->cursor, 20);
+    }
+}
diff --git a/actions/apilists.php b/actions/apilists.php
new file mode 100644 (file)
index 0000000..f520e32
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * List existing lists or create a new list.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @author    Shashi Gowda <connect2shashi@gmail.com>
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Action handler for Twitter list_memeber methods
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Shashi Gowda <connect2shashi@gmail.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ * @see      ApiBareAuthAction
+ */
+
+class ApiListsAction extends ApiBareAuthAction
+{
+    var $lists   = null;
+    var $cursor = 0;
+    var $next_cursor = 0;
+    var $prev_cursor = 0;
+    var $create = false;
+
+    /**
+     * Set the flags for handling the request. List lists created by user if this
+     * is a GET request, create a new list if it is a POST request.
+     *
+     * Takes parameters:
+     *     - user: the user id or nickname
+     * Parameters for POST request
+     *     - name: name of the new list (the people tag itself)
+     *     - mode: (optional) mode for the new list private/public
+     *     - description: (optional) description for the list
+     *
+     * @return boolean success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->create = ($_SERVER['REQUEST_METHOD'] == 'POST');
+
+        if (!$this->create) {
+
+            $this->user = $this->getTargetUser($this->arg('user'));
+
+            if (empty($this->user)) {
+                $this->clientError(_('No such user.'), 404, $this->format);
+                return false;
+            }
+            $this->getLists();
+        }
+
+        return true;
+    }
+
+    /**
+     * require authentication if it is a write action or user is ambiguous
+     *
+     */
+
+    function requiresAuth()
+    {
+        return parent::requiresAuth() ||
+            $this->create || $this->delete;
+    }
+
+    /**
+     * Handle request:
+     *     Show the lists the user has created if the request method is GET
+     *     Create a new list by diferring to handlePost() if it is POST.
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if($this->create) {
+            return $this->handlePost();
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlLists($this->lists, $this->next_cursor, $this->prev_cursor);
+            break;
+        case 'json':
+            $this->showJsonLists($this->lists, $this->next_cursor, $this->prev_cursor);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Create a new list
+     *
+     * @return boolean success
+     */
+
+    function handlePost()
+    {
+        $name=$this->arg('name');
+        if(empty($name)) {
+            // mimick twitter
+            print _("A list's name can't be blank.");
+            exit(1);
+        }
+
+        // twitter creates a new list by appending a number to the end
+        // if the list by the given name already exists
+        // it makes more sense to return the existing list instead
+
+        $private = null;
+        if ($this->arg('mode') === 'public') {
+            $private = false;
+        } else if ($this->arg('mode') === 'private') {
+            $private = true;
+        }
+
+        $list = Profile_list::ensureTag($this->auth_user->id,
+                                        $this->arg('name'),
+                                        $this->arg('description'),
+                                        $private);
+        if (empty($list)) {
+            return false;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($list);
+            break;
+        case 'json':
+            $this->showSingleJsonList($list);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+        return true;
+    }
+
+    /**
+     * Get lists
+     */
+
+    function getLists()
+    {
+        $cursor = (int) $this->arg('cursor', -1);
+
+        // twitter fixes count at 20
+        // there is no argument named count
+        $count = 20;
+        $profile = $this->user->getProfile();
+        $fn = array($profile, 'getOwnedTags');
+
+        list($this->lists,
+             $this->next_cursor,
+             $this->prev_cursor) = Profile_list::getAtCursor($fn, array($this->auth_user), $cursor, $count);
+    }
+
+    function isReadOnly($args)
+    {
+        return false;
+    }
+
+    function lastModified()
+    {
+        if (!$this->create && !empty($this->lists) && (count($this->lists) > 0)) {
+            return strtotime($this->lists[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this list of lists
+     *
+     * Returns an Etag based on the action name, language, user ID and
+     * timestamps of the first and last list the user has joined
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!$this->create && !empty($this->lists) && (count($this->lists) > 0)) {
+
+            $last = count($this->lists) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->lists[0]->created),
+                      strtotime($this->lists[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apilistsubscriber.php b/actions/apilistsubscriber.php
new file mode 100644 (file)
index 0000000..d6816b9
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Check if a user is subscribed to a list
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+class ApiListSubscriberAction extends ApiBareAuthAction
+{
+    var $list   = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+        $this->list = $this->getTargetList($this->arg('user'), $this->arg('list_id'));
+
+        if (empty($this->list)) {
+            $this->clientError(_('Not found'), 404, $this->format);
+            return false;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user'), 404, $this->format);
+            return false;
+        }
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $arr = array('profile_tag_id' => $this->list->id,
+                      'profile_id' => $this->user->id);
+        $sub = Profile_tag_subscription::pkeyGet($arr);
+
+        if(empty($sub)) {
+            $this->clientError(
+                _('The specified user is not a subscriber of this list'),
+                400,
+                $this->format
+            );
+        }
+
+        $user = $this->twitterUserArray($this->user->getProfile(), true);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showTwitterXmlUser($user, 'user', true);
+            break;
+        case 'json':
+            $this->showSingleJsonUser($user);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+}
diff --git a/actions/apilistsubscribers.php b/actions/apilistsubscribers.php
new file mode 100644 (file)
index 0000000..e8468a1
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show/add/remove list subscribers.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apilistusers.php';
+
+class ApiListSubscribersAction extends ApiListUsersAction
+{
+    /**
+     * Subscribe to list
+     *
+     * @return boolean success
+     */
+
+    function handlePost()
+    {
+        $result = Profile_tag_subscription::add($this->list,
+                            $this->auth_user);
+
+        if(empty($result)) {
+            $this->clientError(
+                _('An error occured.'),
+                500,
+                $this->format
+            );
+            return false;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($this->list);
+            break;
+        case 'json':
+            $this->showSingleJsonList($this->list);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            return false;
+            break;
+        }
+    }
+
+    function handleDelete()
+    {
+        $args = array('profile_tag_id' => $this->list->id,
+                      'profile_id' => $this->auth_user->id);
+        $ptag = Profile_tag_subscription::pkeyGet($args);
+
+        if(empty($ptag)) {
+            $this->clientError(
+                _('You are not subscribed to this list'),
+                400,
+                $this->format
+            );
+            return false;
+        }
+
+        Profile_tag_subscription::remove($this->list, $this->auth_user);
+
+        if(empty($result)) {
+            $this->clientError(
+                _('An error occured.'),
+                500,
+                $this->format
+            );
+            return false;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlList($this->list);
+            break;
+        case 'json':
+            $this->showSingleJsonList($this->list);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            return false;
+            break;
+        }
+        return true;
+    }
+
+    function getUsers()
+    {
+        $fn = array($this->list, 'getSubscribers');
+        list($this->users, $this->next_cursor, $this->prev_cursor) =
+            Profile_list::getAtCursor($fn, array(), $this->cursor, 20);
+    }
+}
diff --git a/actions/apilistsubscriptions.php b/actions/apilistsubscriptions.php
new file mode 100644 (file)
index 0000000..764360e
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Get a list of lists a user is subscribed to.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @copyright 2009 StatusNet, Inc.
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+class ApiListSubscriptionsAction extends ApiBareAuthAction
+{
+    var $lists = array();
+    var $cursor = -1;
+    var $next_cursor = 0;
+    var $prev_cursor = 0;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->cursor = (int) $this->arg('cursor', -1);
+        $this->user = $this->getTargetUser($this->arg('user'));
+        $this->getLists();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the lists
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user.'), 404, $this->format);
+            return;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlLists($this->lists, $this->next_cursor, $this->prev_cursor);
+            break;
+        case 'json':
+            $this->showJsonLists($this->lists, $this->next_cursor, $this->prev_cursor);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                400,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    function getLists()
+    {
+        if(empty($this->user)) {
+            return;
+        }
+
+        $profile = $this->user->getProfile();
+        $fn = array($profile, 'getTagSubscriptions');
+        # 20 lists
+        list($this->lists, $this->next_cursor, $this->prev_cursor) =
+                Profile_list::getAtCursor($fn, array(), $this->cursor, 20);
+    }
+}
diff --git a/actions/apitimelinelist.php b/actions/apitimelinelist.php
new file mode 100644 (file)
index 0000000..f28eb59
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a list's notices
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apiprivateauth.php';
+require_once INSTALLDIR . '/lib/atomlistnoticefeed.php';
+
+/**
+ * Returns the most recent notices (default 20) posted to the list specified by ID
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ApiTimelineListAction extends ApiPrivateAuthAction
+{
+
+    var $list   = null;
+    var $notices = array();
+    var $next_cursor = 0;
+    var $prev_cursor = 0;
+    var $cursor = -1;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->cursor = (int) $this->arg('cursor', -1);
+        $this->list = $this->getTargetList($this->arg('user'), $this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->list)) {
+            $this->clientError(_('List not found.'), 404, $this->format);
+            return false;
+        }
+
+        $this->getNotices();
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        // We'll pull common formatting out of this for other formats
+        $atom = new AtomListNoticeFeed($this->list, $this->auth_user);
+
+        $self = $this->getSelfUri();
+
+        switch($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->elementStart('statuses_list',
+                    array('xmlns:statusnet' => 'http://status.net/schema/api/1/'));
+            $this->elementStart('statuses', array('type' => 'array'));
+
+            foreach ($this->notices as $n) {
+                $twitter_status = $this->twitterStatusArray($n);
+                $this->showTwitterXmlStatus($twitter_status);
+            }
+
+            $this->elementEnd('statuses');
+            $this->element('next_cursor', null, $this->next_cursor);
+            $this->element('previous_cursor', null, $this->prev_cursor);
+            $this->elementEnd('statuses_list');
+            $this->endDocument('xml');
+            break;
+        case 'rss':
+            $this->showRssTimeline(
+                $this->notices,
+                $atom->title,
+                $this->list->getUri(),
+                $atom->subtitle,
+                null,
+                $atom->logo,
+                $self
+            );
+            break;
+        case 'atom':
+
+            header('Content-Type: application/atom+xml; charset=utf-8');
+
+            try {
+                $atom->setId($self);
+                $atom->setSelfLink($self);
+                $atom->addEntryFromNotices($this->notices);
+                $this->raw($atom->getString());
+            } catch (Atom10FeedException $e) {
+                $this->serverError(
+                    'Could not generate feed for list - ' . $e->getMessage()
+                );
+                return;
+            }
+
+            break;
+        case 'json':
+            $this->initDocument('json');
+
+            $statuses = array();
+            foreach ($this->notices as $n) {
+                $twitter_status = $this->twitterStatusArray($n);
+                array_push($statuses, $twitter_status);
+            }
+
+            $statuses_list = array('statuses' => $statuses,
+                                   'next_cursor' => $this->next_cusror,
+                                   'next_cursor_str' => strval($this->next_cusror),
+                                   'previous_cursor' => $this->prev_cusror,
+                                   'previous_cursor_str' => strval($this->prev_cusror)
+                                   );
+            $this->showJsonObjects($statuses_list);
+
+            $this->initDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $fn = array($this->list, 'getNotices');
+        list($this->notices, $this->next_cursor, $this->prev_cursor) =
+                Profile_list::getAtCursor($fn, array(), $this->cursor, 20);
+        if (!$this->notices) {
+            $this->notices = array();
+        }
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, list ID and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->list->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
index a69e1a1b42fe688cff0e22dfe76f29d196183bc7..7771455443d217f53a28511de82dce7081664121 100644 (file)
@@ -64,6 +64,7 @@ class ActivityObject
     const BOOKMARK  = 'http://activitystrea.ms/schema/1.0/bookmark';
     const PERSON    = 'http://activitystrea.ms/schema/1.0/person';
     const GROUP     = 'http://activitystrea.ms/schema/1.0/group';
+    const _LIST     = 'http://activitystrea.ms/schema/1.0/list'; // LIST is reserved
     const PLACE     = 'http://activitystrea.ms/schema/1.0/place';
     const COMMENT   = 'http://activitystrea.ms/schema/1.0/comment';
     // ^^^^^^^^^^ tea!
@@ -92,6 +93,7 @@ class ActivityObject
     public $title;
     public $summary;
     public $content;
+    public $owner;
     public $link;
     public $source;
     public $avatarLinks = array();
@@ -168,6 +170,10 @@ class ActivityObject
                 Activity::MEDIA
             );
         }
+        if ($this->type == self::_LIST) {
+            $owner = ActivityUtils::child($this->element, Activity::AUTHOR, Activity::SPEC);
+            $this->owner = new ActivityObject($owner);
+        }
     }
 
     private function _fromAuthor($element)
@@ -520,13 +526,29 @@ class ActivityObject
                                                                                                                          AVATAR_MINI_SIZE);
 
                        $object->poco = PoCo::fromGroup($group);
-
-                       Event::handle('EndActivityObjectFromGroup', array($group, &$object));
+                   Event::handle('EndActivityObjectFromGroup', array($group, &$object));
                }
 
         return $object;
     }
 
+    static function fromPeopletag($ptag)
+    {
+        $object = new ActivityObject();
+        if (Event::handle('StartActivityObjectFromPeopletag', array($ptag, &$object))) {
+            $object->type    = ActivityObject::_LIST;
+
+            $object->id      = $ptag->getUri();
+            $object->title   = $ptag->tag;
+            $object->summary = $ptag->description;
+            $object->link    = $ptag->homeUrl();
+            $object->owner   = Profile::staticGet('id', $ptag->tagger);
+            $object->poco    = PoCo::fromProfile($object->owner);
+                   Event::handle('EndActivityObjectFromPeopletag', array($ptag, &$object));
+        }
+        return $object;
+    }
+
        function outputTo($xo, $tag='activity:object')
        {
                if (!empty($tag)) {
@@ -601,6 +623,11 @@ class ActivityObject
             }
         }
 
+        if(!empty($this->owner)) {
+            $owner = $this->owner->asActivityNoun(self::AUTHOR);
+            $xo->raw($owner);
+        }
+
         if (!empty($this->geopoint)) {
             $xo->element(
                 'georss:point',
index 264351308b55b60c25139abb50a81b7e286d243e..5ee68f28804fcda7eba1589f0e4c682d7cad8cdb 100644 (file)
@@ -59,6 +59,7 @@ class ActivityVerb
     const UNFAVORITE = 'http://ostatus.org/schema/1.0/unfavorite';
     const UNFOLLOW   = 'http://ostatus.org/schema/1.0/unfollow';
     const LEAVE      = 'http://ostatus.org/schema/1.0/leave';
+    const UNTAG      = 'http://ostatus.org/schema/1.0/untag';
 
     // For simple profile-update pings; no content to share.
     const UPDATE_PROFILE = 'http://ostatus.org/schema/1.0/update-profile';
index ebda36db7f0fc07c2e6770e96cc0d16d81780c49..a77d5da13fe6779376274991d367cbd5a22b7e13 100644 (file)
@@ -458,6 +458,32 @@ class ApiAction extends Action
         return $entry;
     }
 
+    function twitterListArray($list)
+    {
+        $profile = Profile::staticGet('id', $list->tagger);
+
+        $twitter_list = array();
+        $twitter_list['id'] = $list->id;
+        $twitter_list['name'] = $list->tag;
+        $twitter_list['full_name'] = '@'.$profile->nickname.'/'.$list->tag;;
+        $twitter_list['slug'] = $list->tag;
+        $twitter_list['description'] = $list->description;
+        $twitter_list['subscriber_count'] = $list->subscriberCount();
+        $twitter_list['member_count'] = $list->taggedCount();
+        $twitter_list['uri'] = $list->getUri();
+
+        if (isset($this->auth_user)) {
+            $twitter_list['following'] = $list->hasSubscriber($this->auth_user);
+        } else {
+            $twitter_list['following'] = false;
+        }
+
+        $twitter_list['mode'] = ($list->private) ? 'private' : 'public';
+        $twitter_list['user'] = $this->twitterUserArray($profile, false);
+
+        return $twitter_list;
+    }
+
     function twitterRssEntryArray($notice)
     {
         $entry = array();
@@ -633,6 +659,20 @@ class ApiAction extends Action
         $this->elementEnd('group');
     }
 
+    function showTwitterXmlList($twitter_list)
+    {
+        $this->elementStart('list');
+        foreach($twitter_list as $element => $value) {
+            if($element == 'user') {
+                $this->showTwitterXmlUser($value, 'user');
+            }
+            else {
+                $this->element($element, null, $value);
+            }
+        }
+        $this->elementEnd('list');
+    }
+
     function showTwitterXmlUser($twitter_user, $role='user', $namespaces=false)
     {
         $attrs = array();
@@ -1110,6 +1150,65 @@ class ApiAction extends Action
         $this->endDocument('xml');
     }
 
+    function showXmlLists($list, $next_cursor=0, $prev_cursor=0)
+    {
+
+        $this->initDocument('xml');
+        $this->elementStart('lists_list');
+        $this->elementStart('lists', array('type' => 'array'));
+
+        if (is_array($list)) {
+            foreach ($list as $l) {
+                $twitter_list = $this->twitterListArray($l);
+                $this->showTwitterXmlList($twitter_list);
+            }
+        } else {
+            while ($list->fetch()) {
+                $twitter_list = $this->twitterListArray($list);
+                $this->showTwitterXmlList($twitter_list);
+            }
+        }
+
+        $this->elementEnd('lists');
+
+        $this->element('next_cursor', null, $next_cursor);
+        $this->element('previous_cursor', null, $prev_cursor);
+
+        $this->elementEnd('lists_list');
+        $this->endDocument('xml');
+    }
+
+    function showJsonLists($list, $next_cursor=0, $prev_cursor=0)
+    {
+        $this->initDocument('json');
+
+        $lists = array();
+
+        if (is_array($list)) {
+            foreach ($list as $l) {
+                $twitter_list = $this->twitterListArray($l);
+                array_push($lists, $twitter_list);
+            }
+        } else {
+            while ($list->fetch()) {
+                $twitter_list = $this->twitterListArray($list);
+                array_push($lists, $twitter_list);
+            }
+        }
+
+        $lists_list = array(
+            'lists' => $lists,
+            'next_cursor' => $next_cursor,
+            'next_cursor_str' => strval($next_cursor),
+            'previous_cursor' => $prev_cursor,
+            'previous_cursor_str' => strval($prev_cursor)
+        );
+
+        $this->showJsonObjects($lists_list);
+
+        $this->endDocument('json');
+    }
+
     function showTwitterXmlUsers($user)
     {
         $this->initDocument('xml');
@@ -1171,6 +1270,22 @@ class ApiAction extends Action
         $this->endDocument('xml');
     }
 
+    function showSingleJsonList($list)
+    {
+        $this->initDocument('json');
+        $twitter_list = $this->twitterListArray($list);
+        $this->showJsonObjects($twitter_list);
+        $this->endDocument('json');
+    }
+
+    function showSingleXmlList($list)
+    {
+        $this->initDocument('xml');
+        $twitter_list = $this->twitterListArray($list);
+        $this->showTwitterXmlList($twitter_list);
+        $this->endDocument('xml');
+    }
+
     function dateTwitter($dt)
     {
         $dateStr = date('d F Y H:i:s', strtotime($dt));
@@ -1464,6 +1579,40 @@ class ApiAction extends Action
         }
     }
 
+    function getTargetList($user=null, $id=null)
+    {
+        $tagger = $this->getTargetUser($user);
+        $list = null;
+
+        if (empty($id)) {
+            $id = $this->arg('id');
+        }
+
+        if($id) {
+            if (is_numeric($id)) {
+                $list = Profile_list::staticGet('id', $id);
+
+                // only if the list with the id belongs to the tagger
+                if(empty($list) || $list->tagger != $tagger->id) {
+                    $list = null;
+                }
+            }
+            if (empty($list)) {
+                $tag = common_canonical_tag($id);
+                $list = Profile_list::getByTaggerAndTag($tagger->id, $tag);
+            }
+
+            if (!empty($list) && $list->private) {
+                if ($this->auth_user->id == $list->tagger) {
+                    return $list;
+                }
+            } else {
+                return $list;
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns query argument or default value if not found. Certain
      * parameters used throughout the API are lightly scrubbed and
diff --git a/lib/apilistusers.php b/lib/apilistusers.php
new file mode 100644 (file)
index 0000000..e4451c7
--- /dev/null
@@ -0,0 +1,207 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base class for list members and list subscribers api.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  API
+ * @package   StatusNet
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+class ApiListUsersAction extends ApiBareAuthAction
+{
+    var $list   = null;
+    var $user   = false;
+    var $create = false;
+    var $delete = false;
+    var $cursor = -1;
+    var $next_cursor = 0;
+    var $prev_cursor = 0;
+    var $users = null;
+
+    function prepare($args)
+    {
+        // delete list member if method is DELETE or if method is POST and an argument
+        // _method is set to DELETE
+        $this->delete = ($_SERVER['REQUEST_METHOD'] == 'DELETE' ||
+                            ($this->trimmed('_method') == 'DELETE' &&
+                             $_SERVER['REQUEST_METHOD'] == 'POST'));
+
+        // add member if method is POST
+        $this->create = (!$this->delete &&
+                         $_SERVER['REQUEST_METHOD'] == 'POST');
+
+        if($this->arg('id')) {
+            $this->user = $this->getTargetUser($this->arg('id'));
+        }
+
+        parent::prepare($args);
+
+        $this->list = $this->getTargetList($this->arg('user'), $this->arg('list_id'));
+
+        if (empty($this->list)) {
+            $this->clientError(_('Not found'), 404, $this->format);
+            return false;
+        }
+
+        if(!$this->create && !$this->delete) {
+            $this->getUsers();
+        }
+        return true;
+    }
+
+    function requiresAuth()
+    {
+        return parent::requiresAuth() ||
+            $this->create || $this->delete;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if($this->delete) {
+            return $this->handleDelete();
+        }
+
+        if($this->create) {
+            return $this->handlePost();
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->elementStart('users_list', array('xmlns:statusnet' =>
+                                         'http://status.net/schema/api/1/'));
+            $this->elementStart('users', array('type' => 'array'));
+
+            if (is_array($this->users)) {
+                foreach ($this->users as $u) {
+                    $twitter_user = $this->twitterUserArray($u, true);
+                    $this->showTwitterXmlUser($twitter_user);
+                }
+            } else {
+                while ($this->users->fetch()) {
+                    $twitter_user = $this->twitterUserArray($this->users, true);
+                    $this->showTwitterXmlUser($twitter_user);
+                }
+            }
+
+            $this->elementEnd('users');
+            $this->element('next_cursor', null, $this->next_cursor);
+            $this->element('previous_cursor', null, $this->prev_cursor);
+            $this->elementEnd('users_list');
+            break;
+        case 'json':
+            $this->initDocument('json');
+
+            $users = array();
+
+            if (is_array($this->users)) {
+                foreach ($this->users as $u) {
+                    $twitter_user = $this->twitterUserArray($u, true);
+                    array_push($users, $twitter_user);
+                }
+            } else {
+                while ($this->users->fetch()) {
+                    $twitter_user = $this->twitterUserArray($this->users, true);
+                    array_push($users, $twitter_user);
+                }
+            }
+
+            $users_list = array('users' => $users,
+                                'next_cursor' => $this->next_cursor,
+                                'next_cursor_str' => strval($this->next_cursor),
+                                'previous_cursor' => $this->prev_cursor,
+                                'previous_cursor_str' => strval($this->prev_cursor));
+
+            $this->showJsonObjects($users_list);
+
+            $this->endDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found.'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    function handlePost()
+    {
+    }
+
+    function handleDelete()
+    {
+    }
+
+    function getUsers()
+    {
+    }
+
+    function isReadOnly($args)
+    {
+        return false;
+    }
+
+    function lastModified()
+    {
+        if(!empty($this->list)) {
+            return strtotime($this->list->modified);
+        }
+        return null;
+    }
+
+    /**
+     * An entity tag for this list
+     *
+     * Returns an Etag based on the action name, language, user ID and
+     * timestamps of the first and last list the user has joined
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->list)) {
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->list->id,
+                      strtotime($this->list->created),
+                      strtotime($this->list->modified))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/lib/atomlistnoticefeed.php b/lib/atomlistnoticefeed.php
new file mode 100644 (file)
index 0000000..fec7e16
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for building an in-memory Atom feed for a particular list's
+ * timeline.
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  Feed
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET'))
+{
+    exit(1);
+}
+
+/**
+ * Class for list notice feeds.  May contain a reference to the list.
+ *
+ * @category Feed
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+class AtomListNoticeFeed extends AtomNoticeFeed
+{
+    private $list;
+    private $tagger;
+
+    /**
+     * Constructor
+     *
+     * @param List    $list    the list for the feed
+     * @param User    $cur     the current authenticated user, if any
+     * @param boolean $indent  flag to turn indenting on or off
+     *
+     * @return void
+     */
+    function __construct($list, $cur = null, $indent = true) {
+        parent::__construct($cur, $indent);
+        $this->list = $list;
+        $this->tagger = Profile::staticGet('id', $list->tagger);
+
+        // TRANS: Title in atom list notice feed. %s is a list name.
+        $title = sprintf(_("Timeline for people tagged #%s by %s"), $list->tag, $this->tagger->nickname);
+        $this->setTitle($title);
+
+        $sitename   = common_config('site', 'name');
+        $subtitle   = sprintf(
+            // TRANS: Message is used as a subtitle in atom list notice feed.
+            // TRANS: %1$s is a list name, %2$s is a site name.
+            _('Updates from %1$s\'s %2$s people tag on %3$s!'),
+            $this->tagger->nickname,
+            $list->tag,
+            $sitename
+        );
+        $this->setSubtitle($subtitle);
+
+        $avatar = $this->tagger->avatarUrl(AVATAR_PROFILE_SIZE);
+        $this->setLogo($avatar);
+
+        $this->setUpdated('now');
+
+        $self = common_local_url('ApiTimelineList',
+                                 array('user' => $this->tagger->nickname,
+                                       'id' => $list->tag,
+                                       'format' => 'atom'));
+        $this->setId($self);
+        $this->setSelfLink($self);
+
+        // FIXME: Stop using activity:subject?
+        $ao = ActivityObject::fromPeopletag($this->list);
+
+        $this->addAuthorRaw($ao->asString('author').
+                            $ao->asString('activity:subject'));
+
+        $this->addLink($this->list->getUri());
+    }
+
+    function getList()
+    {
+        return $this->list;
+    }
+
+}
index ccc4b0978170fc75eaa0395e5bcdf443c39f0301..ee1e4cd84976c4af68cb57af70a3ee6f26d6ff5d 100644 (file)
@@ -766,6 +766,72 @@ class Router
                               'id' => '[a-zA-Z0-9]+',
                               'format' => '(xml|json)'));
 
+            // Lists (people tags)
+
+            $m->connect('api/lists/memberships.:format',
+                        array('action' => 'ApiListMemberships',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/lists/memberships.:format',
+                        array('action' => 'ApiListMemberships',
+                              'user' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/lists/subscriptions.:format',
+                        array('action' => 'ApiListSubscriptions',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/lists/subscriptions.:format',
+                        array('action' => 'ApiListSubscriptions',
+                              'user' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+            $m->connect('api/lists.:format',
+                        array('action' => 'ApiLists',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/lists.:format',
+                        array('action' => 'ApiLists',
+                              'user' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/lists/:id.:format',
+                        array('action' => 'ApiList',
+                              'user' => '[a-zA-Z0-9]+',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/lists/:id/statuses.:format',
+                        array('action' => 'ApiTimelineList',
+                              'user' => '[a-zA-Z0-9]+',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json|rss|atom)'));
+
+            $m->connect('api/:user/:list_id/members.:format',
+                        array('action' => 'ApiListMembers',
+                              'user' => '[a-zA-Z0-9]+',
+                              'list_id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/:list_id/subscribers.:format',
+                        array('action' => 'ApiListSubscribers',
+                              'user' => '[a-zA-Z0-9]+',
+                              'list_id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/:list_id/members/:id.:format',
+                        array('action' => 'ApiListMember',
+                              'user' => '[a-zA-Z0-9]+',
+                              'list_id' => '[a-zA-Z0-9]+',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
+            $m->connect('api/:user/:list_id/subscribers/:id.:format',
+                        array('action' => 'ApiListSubscriber',
+                              'user' => '[a-zA-Z0-9]+',
+                              'list_id' => '[a-zA-Z0-9]+',
+                              'id' => '[a-zA-Z0-9]+',
+                              'format' => '(xml|json)'));
+
             // Tags
             $m->connect('api/statusnet/tags/timeline/:tag.:format',
                         array('action' => 'ApiTimelineTag',