]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.9.x' of git://gitorious.org/statusnet/mainline into 0.9.x
authorBrion Vibber <brion@pobox.com>
Tue, 13 Oct 2009 15:21:55 +0000 (08:21 -0700)
committerBrion Vibber <brion@pobox.com>
Tue, 13 Oct 2009 15:21:55 +0000 (08:21 -0700)
63 files changed:
actions/api.php [deleted file]
actions/apiaccountratelimitstatus.php [new file with mode: 0644]
actions/apiaccountverifycredentials.php [new file with mode: 0644]
actions/apiblockcreate.php [new file with mode: 0644]
actions/apiblockdestroy.php [new file with mode: 0644]
actions/apidirectmessage.php [new file with mode: 0644]
actions/apidirectmessagenew.php [new file with mode: 0644]
actions/apifavoritecreate.php [new file with mode: 0644]
actions/apifavoritedestroy.php [new file with mode: 0644]
actions/apifriendshipscreate.php [new file with mode: 0644]
actions/apifriendshipsdestroy.php [new file with mode: 0644]
actions/apifriendshipsexists.php [new file with mode: 0644]
actions/apifriendshipsshow.php [new file with mode: 0644]
actions/apigroupcreate.php [new file with mode: 0644]
actions/apigroupismember.php [new file with mode: 0644]
actions/apigroupjoin.php [new file with mode: 0644]
actions/apigroupleave.php [new file with mode: 0644]
actions/apigrouplist.php [new file with mode: 0644]
actions/apigrouplistall.php [new file with mode: 0644]
actions/apigroupmembership.php [new file with mode: 0644]
actions/apigroupshow.php [new file with mode: 0644]
actions/apihelptest.php [new file with mode: 0644]
actions/apistatusesdestroy.php [new file with mode: 0644]
actions/apistatusesshow.php [new file with mode: 0644]
actions/apistatusesupdate.php [new file with mode: 0644]
actions/apistatusnetconfig.php [new file with mode: 0644]
actions/apistatusnetversion.php [new file with mode: 0644]
actions/apisubscriptions.php [new file with mode: 0644]
actions/apitimelinefavorites.php [new file with mode: 0644]
actions/apitimelinefriends.php [new file with mode: 0644]
actions/apitimelinegroup.php [new file with mode: 0644]
actions/apitimelinementions.php [new file with mode: 0644]
actions/apitimelinepublic.php [new file with mode: 0644]
actions/apitimelinetag.php [new file with mode: 0644]
actions/apitimelineuser.php [new file with mode: 0644]
actions/apiuserfollowers.php [new file with mode: 0644]
actions/apiuserfriends.php [new file with mode: 0644]
actions/apiusershow.php [new file with mode: 0644]
actions/twitapiaccount.php [deleted file]
actions/twitapiblocks.php [deleted file]
actions/twitapidirect_messages.php [deleted file]
actions/twitapifavorites.php [deleted file]
actions/twitapifriendships.php [deleted file]
actions/twitapigroups.php [deleted file]
actions/twitapihelp.php [deleted file]
actions/twitapinotifications.php [deleted file]
actions/twitapisearchatom.php
actions/twitapisearchjson.php
actions/twitapistatuses.php [deleted file]
actions/twitapistatusnet.php [deleted file]
actions/twitapitags.php [deleted file]
actions/twitapitrends.php
actions/twitapiusers.php [deleted file]
lib/api.php [new file with mode: 0644]
lib/apiauth.php [new file with mode: 0644]
lib/apibareauth.php [new file with mode: 0644]
lib/router.php
lib/twitter.php
lib/twitterapi.php [deleted file]
lib/twitterbasicauthclient.php
plugins/Realtime/RealtimePlugin.php
plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php [new file with mode: 0644]
theme/default/css/display.css

diff --git a/actions/api.php b/actions/api.php
deleted file mode 100644 (file)
index 1bc90de..0000000
+++ /dev/null
@@ -1,306 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- *
- * @category Actions
- * @package  Actions
- * @author   Evan Prodromou <evan@status.net>
- * @author   Brenda Wallace <shiny@cpan.org>
- * @author   Jeffery To <jeffery.to@gmail.com>
- * @author   Robin Millette <millette@controlyourself.ca>
- * @author   Tom Adams <tom@holizz.com>
- * @author   Christopher Vollick <psycotica0@gmail.com>
- * @author   CiaranG <ciaran@ciarang.com>
- * @author   Craig Andrews <candrews@integralblue.com>
- * @author   Gina Haeussge <osd@foosel.net>
- * @author   Mike Cochrane <mikec@mikenz.geek.nz>
- * @author   Sarven Capadisli <csarven@status.net>
- * @license  GNU Affero General Public License http://www.gnu.org/licenses/
- * @link     http://status.net
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-class ApiAction extends Action
-{
-
-    var $user;
-    var $content_type;
-    var $api_arg;
-    var $api_method;
-    var $api_action;
-    var $auth_user;
-    var $auth_pw;
-
-    function handle($args)
-    {
-        parent::handle($args);
-
-        $this->api_action = $this->arg('apiaction');
-        $method = $this->arg('method');
-        $argument = $this->arg('argument');
-        $this->basic_auth_process_header();
-
-        if (isset($argument)) {
-            $cmdext = explode('.', $argument);
-            $this->api_arg =  $cmdext[0];
-            $this->api_method = $method;
-            $this->content_type = strtolower($cmdext[1]);
-        } else {
-
-            //Requested format / content-type will be an extension on the method
-            $cmdext = explode('.', $method);
-            $this->api_method = $cmdext[0];
-            $this->content_type = strtolower($cmdext[1]);
-        }
-
-        if ($this->requires_auth()) {
-            if (!isset($this->auth_user)) {
-
-                //This header makes basic auth go
-                header('WWW-Authenticate: Basic realm="StatusNet API"');
-
-                //If the user hits cancel -- bam!
-                $this->show_basic_auth_error();
-            } else {
-                $nickname = $this->auth_user;
-                $password = $this->auth_pw;
-                $user = common_check_user($nickname, $password);
-
-                if ($user) {
-                    $this->user = $user;
-                    $this->process_command();
-                } else {
-                    //basic authentication failed
-                    list($proxy, $ip) = common_client_ip();
-
-                    common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
-                    $this->show_basic_auth_error();
-                }
-            }
-        } else {
-
-            // Caller might give us a username even if not required
-            if (isset($this->auth_user)) {
-                $user = User::staticGet('nickname', $this->auth_user);
-                if ($user) {
-                    $this->user = $user;
-                }
-                //Twitter doesn't throw an error if the user isn't found
-            }
-
-            $this->process_command();
-        }
-    }
-
-    function process_command()
-    {
-        $action = "twitapi$this->api_action";
-        $actionfile = INSTALLDIR."/actions/$action.php";
-
-        if (file_exists($actionfile)) {
-            include_once $actionfile;
-            $action_class = ucfirst($action)."Action";
-            $action_obj = new $action_class();
-
-            if (!$action_obj->prepare($this->args)) {
-                return;
-            }
-
-            if (method_exists($action_obj, $this->api_method)) {
-                $apidata = array(    'content-type' => $this->content_type,
-                                    'api_method' => $this->api_method,
-                                    'api_arg' => $this->api_arg,
-                                    'user' => $this->user);
-
-                call_user_func(array($action_obj, $this->api_method), $_REQUEST, $apidata);
-            } else {
-                $this->clientError("API method not found!", $code = 404);
-            }
-        } else {
-            $this->clientError("API method not found!", $code = 404);
-        }
-    }
-
-    // Whitelist of API methods that don't need authentication
-    function requires_auth()
-    {
-        static $noauth = array( 'statuses/public_timeline',
-                                'statuses/show',
-                                'users/show',
-                                'help/test',
-                                'help/downtime_schedule',
-                                'statusnet/version',
-                                'statusnet/config',
-                                'statusnet/wadl',
-                                'tags/timeline',
-                                'oembed/oembed',
-                                'groups/show',
-                                'groups/timeline',
-                                'groups/list_all',
-                                'groups/membership',
-                                'groups/is_member',
-                                'groups/timeline');
-
-        static $bareauth = array('statuses/user_timeline',
-                                 'statuses/friends_timeline',
-                                'statuses/home_timeline',
-                                 'statuses/friends',
-                                 'statuses/replies',
-                                 'statuses/mentions',
-                                 'statuses/followers',
-                                 'favorites/favorites',
-                                 'friendships/show',
-                                 'groups/list_groups');
-
-        $fullname = "$this->api_action/$this->api_method";
-
-        // If the site is "private", all API methods except statusnet/config
-        // need authentication
-
-        if (common_config('site', 'private')) {
-            return $fullname != 'statusnet/config' || false;
-        }
-
-        // bareauth: only needs auth if without an argument or query param specifying user
-
-        if (in_array($fullname, $bareauth)) {
-
-            // Special case: friendships/show only needs auth if source_id or
-            // source_screen_name is not specified as a param
-
-            if ($fullname == 'friendships/show') {
-
-                $source_id          = $this->arg('source_id');
-                $source_screen_name = $this->arg('source_screen_name');
-
-                if (empty($source_id) && empty($source_screen_name)) {
-                    return true;
-                }
-
-                return false;
-            }
-
-            // if all of these are empty, auth is required
-
-            $id          = $this->arg('id');
-            $user_id     = $this->arg('user_id');
-            $screen_name = $this->arg('screen_name');
-
-            if (empty($this->api_arg)
-                && empty($id)
-                && empty($user_id)
-                && empty($screen_name)
-            ) {
-                return true;
-            } else {
-                return false;
-            }
-
-        } else if (in_array($fullname, $noauth)) {
-
-            // noauth: never needs auth
-
-            return false;
-        } else {
-
-            // everybody else needs auth
-
-            return true;
-        }
-    }
-
-    function basic_auth_process_header()
-    {
-        if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) {
-            $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
-        }
-
-        if (isset($_SERVER['PHP_AUTH_USER'])) {
-            $this->auth_user = $_SERVER['PHP_AUTH_USER'];
-            $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
-        } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) {
-            // decode the HTTP_AUTHORIZATION header on php-cgi server self
-            // on fcgid server the header name is AUTHORIZATION
-
-            $auth_hash = base64_decode(substr($authorization_header, 6));
-            list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
-
-            // set all to null on a empty basic auth request
-            if ($this->auth_user == "") {
-                $this->auth_user = null;
-                $this->auth_pw = null;
-            }
-        } else {
-            $this->auth_user = null;
-            $this->auth_pw = null;
-        }
-    }
-
-    function show_basic_auth_error()
-    {
-        header('HTTP/1.1 401 Unauthorized');
-        $msg = 'Could not authenticate you.';
-
-        if ($this->content_type == 'xml') {
-            header('Content-Type: application/xml; charset=utf-8');
-            $this->startXML();
-            $this->elementStart('hash');
-            $this->element('error', null, $msg);
-            $this->element('request', null, $_SERVER['REQUEST_URI']);
-            $this->elementEnd('hash');
-            $this->endXML();
-        } else if ($this->content_type == 'json') {
-            header('Content-Type: application/json; charset=utf-8');
-            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
-            print(json_encode($error_array));
-        } else {
-            header('Content-type: text/plain');
-            print "$msg\n";
-        }
-    }
-
-    function isReadOnly($args)
-    {
-        $apiaction = $args['apiaction'];
-        $method = $args['method'];
-
-        list($cmdtext, $fmt) = explode('.', $method);
-
-        static $write_methods = array(
-            'account' => array('update_location', 'update_delivery_device', 'end_session'),
-            'blocks' => array('create', 'destroy'),
-            'direct_messages' => array('create', 'destroy'),
-            'favorites' => array('create', 'destroy'),
-            'friendships' => array('create', 'destroy'),
-            'help' => array(),
-            'notifications' => array('follow', 'leave'),
-            'statuses' => array('update', 'destroy'),
-            'users' => array()
-        );
-
-        if (array_key_exists($apiaction, $write_methods)) {
-            if (!in_array($cmdtext, $write_methods[$apiaction])) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
diff --git a/actions/apiaccountratelimitstatus.php b/actions/apiaccountratelimitstatus.php
new file mode 100644 (file)
index 0000000..96179f1
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Dummy action that emulates Twitter's rate limit status API resource
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * We don't have a rate limit, but some clients check this method.
+ * It always returns the same thing: 150 hits left.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiAccountRateLimitStatusAction extends ApiBareAuthAction
+{
+
+    /**
+     * Handle the request
+     *
+     * Return some Twitter-ish data about API limits
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        $reset   = new DateTime();
+        $reset->modify('+1 hour');
+
+        $this->initDocument($this->format);
+
+         if ($this->format == 'xml') {
+             $this->elementStart('hash');
+             $this->element('remaining-hits', array('type' => 'integer'), 150);
+             $this->element('hourly-limit', array('type' => 'integer'), 150);
+             $this->element(
+                 'reset-time', array('type' => 'datetime'),
+                 common_date_iso8601($reset->format('r'))
+             );
+             $this->element(
+                 'reset_time_in_seconds',
+                 array('type' => 'integer'),
+                 strtotime('+1 hour')
+             );
+             $this->elementEnd('hash');
+         } elseif ($this->format == 'json') {
+             $out = array(
+                 'reset_time_in_seconds' => strtotime('+1 hour'),
+                 'remaining_hits' => 150,
+                 'hourly_limit' => 150,
+                 'reset_time' => common_date_rfc2822(
+                     $reset->format('r')
+                  )
+             );
+             print json_encode($out);
+         }
+
+         $this->endDocument($this->format);
+    }
+
+}
+
diff --git a/actions/apiaccountverifycredentials.php b/actions/apiaccountverifycredentials.php
new file mode 100644 (file)
index 0000000..08b201d
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Test if supplied user credentials are valid.
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Check a user's credentials. Returns an HTTP 200 OK response code and a
+ * representation of the requesting user if authentication was successful;
+ * returns a 401 status code and an error message if not.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiAccountVerifyCredentialsAction extends ApiAuthAction
+{
+
+    /**
+     * Handle the request
+     *
+     * Check whether the credentials are valid and output the result
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        switch ($this->format) {
+        case 'xml':
+        case 'json':
+            $args['id'] = $this->auth_user->id;
+            $action_obj = new ApiUserShowAction();
+            if ($action_obj->prepare($args)) {
+                $action_obj->handle($args);
+            }
+            break;
+        default:
+            header('Content-Type: text/html; charset=utf-8');
+            print 'Authorized';
+        }
+
+    }
+
+}
diff --git a/actions/apiblockcreate.php b/actions/apiblockcreate.php
new file mode 100644 (file)
index 0000000..1cab2df
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Block a user via the 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
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Blocks the user specified in the ID parameter as the authenticating user.
+ * Destroys a friendship to the blocked user if it exists. Returns the
+ * blocked user in the requested format when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiBlockCreateAction extends ApiAuthAction
+{
+    var $other   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user) || empty($this->other)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if ($this->user->hasBlocked($this->other)
+            || $this->user->block($this->other)
+        ) {
+            $this->initDocument($this->format);
+            $this->showProfile($this->other, $this->format);
+            $this->endDocument($this->format);
+        } else {
+            $this->serverError(_('Block user failed.'), 500, $this->format);
+        }
+
+    }
+
+}
+
diff --git a/actions/apiblockdestroy.php b/actions/apiblockdestroy.php
new file mode 100644 (file)
index 0000000..16dbf94
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Un-block a user via the 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
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Un-blocks the user specified in the ID parameter for the authenticating user.
+ * Returns the un-blocked user in the requested format when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiBlockDestroyAction extends ApiAuthAction
+{
+    var $other   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user) || empty($this->other)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if (!$this->user->hasBlocked($this->other)
+            || $this->user->unblock($this->other)
+        ) {
+            $this->initDocument($this->format);
+            $this->showProfile($this->other, $this->format);
+            $this->endDocument($this->format);
+        } else {
+            $this->serverError(_('Unblock user failed.'));
+        }
+
+    }
+
+}
+
diff --git a/actions/apidirectmessage.php b/actions/apidirectmessage.php
new file mode 100644 (file)
index 0000000..a21fe86
--- /dev/null
@@ -0,0 +1,375 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a the direct messages from or to 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    Adrian Lang <mail@adrianlang.de>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Show a list of direct messages from or to the authenticating user
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiDirectMessageAction extends ApiAuthAction
+{
+    var $messages     = null;
+    var $title        = null;
+    var $subtitle     = null;
+    var $link         = null;
+    var $selfuri_base = null;
+    var $id           = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $server   = common_root_url();
+        $taguribase = common_config('integration', 'taguri');
+
+        if ($this->arg('sent')) {
+
+            // Action was called by /api/direct_messages/sent.format
+
+            $this->title = sprintf(
+                _("Direct messages from %s"),
+                $this->user->nickname
+            );
+            $this->subtitle = sprintf(
+                _("All the direct messages sent from %s"),
+                $this->user->nickname
+            );
+            $this->link = $server . $this->user->nickname . '/outbox';
+            $this->selfuri_base = common_root_url() . 'api/direct_messages/sent';
+            $this->id = "tag:$taguribase:SentDirectMessages:" . $this->user->id;
+        } else {
+            $this->title = sprintf(
+                _("Direct messages to %s"),
+                $this->user->nickname
+            );
+            $this->subtitle = sprintf(
+                _("All the direct messages sent to %s"),
+                $this->user->nickname
+            );
+            $this->link = $server . $this->user->nickname . '/inbox';
+            $this->selfuri_base = common_root_url() . 'api/direct_messages';
+            $this->id = "tag:$taguribase:DirectMessages:" . $this->user->id;
+        }
+
+        $this->messages = $this->getMessages();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the messages
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showMessages();
+    }
+
+    /**
+     * Show the messages
+     *
+     * @return void
+     */
+
+    function showMessages()
+    {
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlDirectMessages();
+            break;
+        case 'rss':
+            $this->showRssDirectMessages();
+            break;
+        case 'atom':
+            $this->showAtomDirectMessages();
+            break;
+        case 'json':
+            $this->showJsonDirectMessages();
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getMessages()
+    {
+        $message  = new Message();
+
+        if ($this->arg('sent')) {
+            $message->from_profile = $this->user->id;
+        } else {
+            $message->to_profile = $this->user->id;
+        }
+
+        if (!empty($this->max_id)) {
+            $message->whereAdd('id <= ' . $this->max_id);
+        }
+
+        if (!empty($this->since_id)) {
+            $message->whereAdd('id > ' . $this->since_id);
+        }
+
+        if (!empty($since)) {
+            $d = date('Y-m-d H:i:s', $this->since);
+            $message->whereAdd("created > '$d'");
+        }
+
+        $message->orderBy('created DESC, id DESC');
+        $message->limit((($this->page - 1) * $this->count), $this->count);
+        $message->find();
+
+        $messages = array();
+
+        while ($message->fetch()) {
+            $messages[] = clone($message);
+        }
+
+        return $messages;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this notice last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->messages)) {
+            return strtotime($this->messages[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * Shows a list of direct messages as Twitter-style XML array
+     *
+     * @return void
+     */
+
+    function showXmlDirectMessages()
+    {
+        $this->initDocument('xml');
+        $this->elementStart('direct-messages', array('type' => 'array'));
+
+        foreach ($this->messages as $m) {
+            $dm_array = $this->directMessageArray($m);
+            $this->showXmlDirectMessage($dm_array);
+        }
+
+        $this->elementEnd('direct-messages');
+        $this->endDocument('xml');
+    }
+
+    /**
+     * Shows a list of direct messages as a JSON encoded array
+     *
+     * @return void
+     */
+
+    function showJsonDirectMessages()
+    {
+        $this->initDocument('json');
+
+        $dmsgs = array();
+
+        foreach ($this->messages as $m) {
+            $dm_array = $this->directMessageArray($m);
+            array_push($dmsgs, $dm_array);
+        }
+
+        $this->showJsonObjects($dmsgs);
+        $this->endDocument('json');
+    }
+
+    /**
+     * Shows a list of direct messages as RSS items
+     *
+     * @return void
+     */
+
+    function showRssDirectMessages()
+    {
+        $this->initDocument('rss');
+
+        $this->element('title', null, $this->title);
+
+        $this->element('link', null, $this->link);
+        $this->element('description', null, $this->subtitle);
+        $this->element('language', null, 'en-us');
+
+        $this->element(
+            'atom:link',
+            array(
+                'type' => 'application/rss+xml',
+                'href' => $this->selfuri_base . '.rss',
+                'rel' => self
+                ),
+            null
+        );
+        $this->element('ttl', null, '40');
+
+        foreach ($this->messages as $m) {
+            $entry = $this->rssDirectMessageArray($m);
+            $this->showTwitterRssItem($entry);
+        }
+
+        $this->endTwitterRss();
+    }
+
+    /**
+     * Shows a list of direct messages as Atom entries
+     *
+     * @return void
+     */
+
+    function showAtomDirectMessages()
+    {
+        $this->initDocument('atom');
+
+        $this->element('title', null, $this->title);
+        $this->element('id', null, $this->id);
+
+        $selfuri = common_root_url() . 'api/direct_messages.atom';
+
+        $this->element(
+            'link', array(
+            'href' => $this->link,
+            'rel' => 'alternate',
+            'type' => 'text/html'),
+            null
+        );
+        $this->element(
+            'link', array(
+            'href' => $this->selfuri_base . '.atom', 'rel' => 'self',
+            'type' => 'application/atom+xml'),
+            null
+        );
+        $this->element('updated', null, common_date_iso8601('now'));
+        $this->element('subtitle', null, $this->subtitle);
+
+        foreach ($this->messages as $m) {
+            $entry = $this->rssDirectMessageArray($m);
+            $this->showTwitterAtomEntry($entry);
+        }
+
+        $this->endDocument('atom');
+    }
+
+    /**
+     * An entity tag for this notice
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the notice
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->messages)) {
+
+            $last = count($this->messages) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      strtotime($this->messages[0]->created),
+                      strtotime($this->messages[$last]->created)
+                )
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apidirectmessagenew.php b/actions/apidirectmessagenew.php
new file mode 100644 (file)
index 0000000..fa6cafb
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Send a direct message via the 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
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Creates a new direct message from the authenticating user to
+ * the user specified by id.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiDirectMessageNewAction extends ApiAuthAction
+{
+    var $source  = null;
+    var $other   = null;
+    var $content = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->source = $this->trimmed('source'); // Not supported by Twitter.
+
+        $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
+        if (empty($thtis->source) || in_array($this->source, $reserved_sources)) {
+            $source = 'api';
+        }
+
+        $this->content = $this->trimmed('text');
+
+        $this->user  = $this->auth_user;
+
+        $user_param  = $this->trimmed('user');
+        $user_id     = $this->arg('user_id');
+        $screen_name = $this->trimmed('screen_name');
+
+        if (isset($user_param) || isset($user_id) || isset($screen_name)) {
+            $this->other = $this->getTargetUser($user_param);
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->content)) {
+            $this->clientError(
+                _('No message text!'),
+                406,
+                $this->format
+            );
+        } else {
+            $content_shortened = common_shorten_links($this->content);
+            if (Message::contentTooLong($content_shortened)) {
+                $this->clientError(
+                    sprintf(
+                        _('That\'s too long. Max message size is %d chars.'),
+                        Message::maxContent()
+                    ),
+                    406,
+                    $this->format
+                );
+                return;
+            }
+        }
+
+        if (empty($this->other)) {
+            $this->clientError(_('Recipient user not found.'), 403, $this->format);
+            return;
+        } else if (!$this->user->mutuallySubscribed($this->other)) {
+            $this->clientError(
+                _('Can\'t send direct messages to users who aren\'t your friend.'),
+                403,
+                $this->format
+            );
+            return;
+        } else if ($this->user->id == $this->other->id) {
+
+            // Note: sending msgs to yourself is allowed by Twitter
+
+            $errmsg = 'Don\'t send a message to yourself; ' .
+                   'just say it to yourself quietly instead.'
+
+            $this->clientError(_($errmsg), 403, $this->format);
+            return;
+        }
+
+        $message = Message::saveNew(
+            $this->user->id,
+            $this->other->id,
+            html_entity_decode($this->content, ENT_NOQUOTES, 'UTF-8'),
+            $this->source
+        );
+
+        if (is_string($message)) {
+            $this->serverError($message);
+            return;
+        }
+
+        mail_notify_message($message, $this->user, $this->other);
+
+        if ($this->format == 'xml') {
+            $this->showSingleXmlDirectMessage($message);
+        } elseif ($this->format == 'json') {
+            $this->showSingleJsondirectMessage($message);
+        }
+    }
+
+}
+
diff --git a/actions/apifavoritecreate.php b/actions/apifavoritecreate.php
new file mode 100644 (file)
index 0000000..a80a649
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Add a notice to a user's list of favorite notices via the 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
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Favorites the status specified in the ID parameter as the authenticating user.
+ * Returns the favorite status when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiFavoriteCreateAction extends ApiAuthAction
+{
+    var $notice = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->notice = Notice::staticGet($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->notice)) {
+            $this->clientError(
+                _('No status found with that ID.'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        // Note: Twitter lets you fave things repeatedly via API.
+
+        if ($this->user->hasFave($this->notice)) {
+            $this->clientError(
+                _('This status is already a favorite!'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $fave = Fave::addNew($this->user, $this->notice);
+
+        if (empty($fave)) {
+            $this->clientError(
+                _('Could not create favorite.')
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $this->notify($fave, $this->notice, $this->user);
+        $this->user->blowFavesCache();
+
+        if ($this->format == 'xml') {
+            $this->showSingleXmlStatus($this->notice);
+        } elseif ($this->format == 'json') {
+            $this->show_single_json_status($this->notice);
+        }
+    }
+
+    /**
+     * Notify the author of the favorite that the user likes their notice
+     *
+     * @param Favorite $fave   the favorite in question
+     * @param Notice   $notice the notice that's been faved
+     * @param User     $user   the user doing the favoriting
+     *
+     * @return void
+     */
+    function notify($fave, $notice, $user)
+    {
+        $other = User::staticGet('id', $notice->profile_id);
+        if ($other && $other->id != $user->id) {
+            if ($other->email && $other->emailnotifyfav) {
+                mail_notify_fave($other, $user, $notice);
+            }
+            // XXX: notify by IM
+            // XXX: notify by SMS
+        }
+    }
+
+}
diff --git a/actions/apifavoritedestroy.php b/actions/apifavoritedestroy.php
new file mode 100644 (file)
index 0000000..f131d1c
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Remote a notice from a user's list of favorite notices via the 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
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Un-favorites the status specified in the ID parameter as the authenticating user.
+ * Returns the un-favorited status in the requested format when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiFavoriteDestroyAction extends ApiAuthAction
+{
+
+    var $notice = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->notice = Notice::staticGet($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->notice)) {
+            $this->clientError(
+                _('No status found with that ID.'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        $fave            = new Fave();
+        $fave->user_id   = $this->user->id;
+        $fave->notice_id = $this->notice->id;
+
+        if (!$fave->find(true)) {
+            $this->clientError(
+                _('That status is not a favorite!'),
+                403,
+                $this->favorite
+            );
+            return;
+        }
+
+        $result = $fave->delete();
+
+        if (!$result) {
+            common_log_db_error($fave, 'DELETE', __FILE__);
+            $this->clientError(
+                _('Could not delete favorite.'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        $this->user->blowFavesCache();
+
+        if ($this->format == 'xml') {
+            $this->showSingleXmlStatus($this->notice);
+        } elseif ($this->format == 'json') {
+            $this->show_single_json_status($this->notice);
+        }
+    }
+
+}
diff --git a/actions/apifriendshipscreate.php b/actions/apifriendshipscreate.php
new file mode 100644 (file)
index 0000000..a824e73
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Subscribe to a user via the 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
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Allows the authenticating users to follow (subscribe) the user specified in
+ * the ID parameter.  Returns the befriended user in the requested format when
+ * successful.  Returns a string describing the failure condition when unsuccessful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiFriendshipsCreateAction extends ApiAuthAction
+{
+    var $other  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->other)) {
+            $this->clientError(
+                _('Could not follow user: User not found.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        if ($this->user->isSubscribed($this->other)) {
+            $errmsg = sprintf(
+                _('Could not follow user: %s is already on your list.'),
+                $this->other->nickname
+            );
+            $this->clientError($errmsg, 403, $this->format);
+            return;
+        }
+
+        $result = subs_subscribe_to($this->user, $this->other);
+
+        if (is_string($result)) {
+            $this->clientError($result, 403, $this->format);
+            return;
+        }
+
+        $this->initDocument($this->format);
+        $this->showProfile($this->other, $this->format);
+        $this->endDocument($this->format);
+    }
+
+}
diff --git a/actions/apifriendshipsdestroy.php b/actions/apifriendshipsdestroy.php
new file mode 100644 (file)
index 0000000..3d9b7e0
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Unsubscribe to a user via 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
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Allows the authenticating users to unfollow (unsubscribe) the user specified in
+ * the ID parameter.  Returns the unfollowed user in the requested format when
+ * successful.  Returns a string describing the failure condition when unsuccessful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiFriendshipsDestroyAction extends ApiAuthAction
+{
+    var $other  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->other)) {
+            $this->clientError(
+                _('Could not unfollow user: User not found.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        // Don't allow unsubscribing from yourself!
+
+        if ($this->user->id == $this->other->id) {
+            $this->clientError(
+                _("You cannot unfollow yourself!"),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $result = subs_unsubscribe_user($this->user, $this->other->nickname);
+
+        if (is_string($result)) {
+            $this->clientError($result, 403, $this->format);
+            return;
+        }
+
+        $this->initDocument($this->format);
+        $this->showProfile($this->other, $this->format);
+        $this->endDocument($this->format);
+    }
+
+}
diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php
new file mode 100644 (file)
index 0000000..ae50c51
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show whether there is a friendship between two users
+ *
+ * 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    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Tests for the existence of friendship between two users. Will return true if
+ * user_a follows user_b, otherwise will return false.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiFriendshipsExistsAction extends ApiAction
+{
+    var $user_a = null;
+    var $user_b = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $user_a_id = $this->trimmed('user_a');
+        $user_b_id = $this->trimmed('user_b');
+
+        common_debug("user_a = " . $user_a_id);
+        common_debug("user_b = " . $user_b_id);
+
+
+        $this->user_a = $this->getTargetUser($user_a_id);
+
+        if (empty($this->user_a)) {
+            common_debug('gargargra');
+        }
+
+        $this->user_b = $this->getTargetUser($user_b_id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->user_a) || empty($this->user_b)) {
+            $this->clientError(
+                _('Two user ids or screen_names must be supplied.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        $result = $this->user_a->isSubscribed($this->user_b);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->element('friends', null, $result);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            print json_encode($result);
+            $this->endDocument('json');
+            break;
+        default:
+            break;
+        }
+    }
+
+}
diff --git a/actions/apifriendshipsshow.php b/actions/apifriendshipsshow.php
new file mode 100644 (file)
index 0000000..8fc4367
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show information about the relationship between two users
+ *
+ * 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    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Outputs detailed information about the relationship between two users
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiFriendshipsShowAction extends ApiBareAuthAction
+{
+    var $source = null;
+    var $target = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $source_id          = (int)$this->trimmed('source_id');
+        $source_screen_name = $this->trimmed('source_screen_name');
+        $target_id          = (int)$this->trimmed('target_id');
+        $target_screen_name = $this->trimmed('target_screen_name');
+
+        if (!empty($source_id)) {
+            $this->source = User::staticGet($source_id);
+        } elseif (!empty($source_screen_name)) {
+            $this->source = User::staticGet('nickname', $source_screen_name);
+        } else {
+            $this->source = $this->auth_user;
+        }
+
+        if (!empty($target_id)) {
+            $this->target = User::staticGet($target_id);
+        } elseif (!empty($target_screen_name)) {
+            $this->target = User::staticGet('nickname', $target_screen_name);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Determines whether this API resource requires auth.  Overloaded to look
+     * return true in case source_id and source_screen_name are both empty
+     *
+     * @return boolean true or false
+     */
+
+    function requiresAuth()
+    {
+        if (common_config('site', 'private')) {
+            return true;
+        }
+
+        $source_id          = $this->trimmed('source_id');
+        $source_screen_name = $this->trimmed('source_screen_name');
+
+        if (empty($source_id) && empty($source_screen_name)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), 404);
+            return;
+        }
+
+        if (empty($this->source)) {
+            $this->clientError(
+                _('Could not determine source user.'),
+                404
+             );
+            return;
+        }
+
+        if (empty($this->target)) {
+            $this->clientError(
+                _('Could not find target user.'),
+                404
+            );
+            return;
+        }
+
+        $result = $this->twitterRelationshipArray($this->source, $this->target);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->showTwitterXmlRelationship($result[relationship]);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            print json_encode($result);
+            $this->endDocument('json');
+            break;
+        default:
+            break;
+        }
+
+    }
+
+}
diff --git a/actions/apigroupcreate.php b/actions/apigroupcreate.php
new file mode 100644 (file)
index 0000000..cdb2afb
--- /dev/null
@@ -0,0 +1,381 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Create a group via the 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
+ * @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.
+ * @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';
+
+/**
+ * Make a new group. Sets the authenticated user as the administrator of the group.
+ *
+ * @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 ApiGroupCreateAction extends ApiAuthAction
+{
+    var $group       = null;
+    var $nickname    = null;
+    var $fullname    = null;
+    var $homepage    = null;
+    var $description = null;
+    var $location    = null;
+    var $aliasstring = null;
+    var $aliases     = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user  = $this->auth_user;
+
+        $this->nickname    = $this->arg('nickname');
+        $this->fullname    = $this->arg('full_name');
+        $this->homepage    = $this->arg('homepage');
+        $this->description = $this->arg('description');
+        $this->location    = $this->arg('location');
+        $this->aliasstring = $this->arg('aliases');
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new group
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!common_config('inboxes', 'enabled')) {
+            $this->serverError(
+                _('Inboxes must be enabled for groups to work'),
+                400,
+                $this->format
+            );
+            return false;
+        }
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+             $this->clientError(
+                 _('This method requires a POST.'),
+                 400,
+                 $this->format
+             );
+             return;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if ($this->validateParams() == false) {
+            return;
+        }
+
+        $group = new User_group();
+
+        $group->query('BEGIN');
+
+        $group->nickname    = $this->nickname;
+        $group->fullname    = $this->fullname;
+        $group->homepage    = $this->homepage;
+        $group->description = $this->description;
+        $group->location    = $this->location;
+        $group->created     = common_sql_now();
+
+        $result = $group->insert();
+
+        if (!$result) {
+            common_log_db_error($group, 'INSERT', __FILE__);
+            $this->serverError(
+                _('Could not create group.'),
+                500,
+                $this->format
+            );
+            return;
+        }
+
+        $result = $group->setAliases($this->aliases);
+
+        if (!$result) {
+            $this->serverError(
+                _('Could not create aliases.'),
+                500,
+                $this->format
+            );
+            return;
+        }
+
+        $member = new Group_member();
+
+        $member->group_id   = $group->id;
+        $member->profile_id = $this->user->id;
+        $member->is_admin   = 1;
+        $member->created    = $group->created;
+
+        $result = $member->insert();
+
+        if (!$result) {
+            common_log_db_error($member, 'INSERT', __FILE__);
+            $this->serverError(
+                _('Could not set group membership.'),
+                500,
+                $this->format
+            );
+            return;
+        }
+
+        $group->query('COMMIT');
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlGroup($group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($group);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+
+    }
+
+    /**
+     * Validate params for the new group
+     *
+     * @return void
+     */
+
+    function validateParams()
+    {
+        $valid = Validate::string(
+            $this->nickname, array(
+                'min_length' => 1,
+                'max_length' => 64,
+                'format' => NICKNAME_FMT
+            )
+        );
+
+        if (!$valid) {
+            $this->clientError(
+                _(
+                    'Nickname must have only lowercase letters ' .
+                    'and numbers and no spaces.'
+                ),
+                403,
+                $this->format
+            );
+            return false;
+        } elseif ($this->groupNicknameExists($this->nickname)) {
+            $this->clientError(
+                _('Nickname already in use. Try another one.'),
+                403,
+                $this->format
+            );
+            return false;
+        } else if (!User_group::allowedNickname($this->nickname)) {
+            $this->clientError(
+                _('Not a valid nickname.'),
+                403,
+                $this->format
+            );
+            return false;
+
+        } elseif (
+            !is_null($this->homepage)
+            && strlen($this->homepage) > 0
+            && !Validate::uri(
+                $this->homepage, array(
+                    'allowed_schemes' =>
+                    array('http', 'https')
+                )
+            )) {
+            $this->clientError(
+                _('Homepage is not a valid URL.'),
+                403,
+                $this->format
+            );
+            return false;
+        } elseif (
+            !is_null($this->fullname)
+            && mb_strlen($this->fullname) > 255) {
+                $this->clientError(
+                    _('Full name is too long (max 255 chars).'),
+                    403,
+                    $this->format
+                );
+            return false;
+        } elseif (User_group::descriptionTooLong($this->description)) {
+            $this->clientError(
+                sprintf(
+                    _('Description is too long (max %d chars).'),
+                    User_group::maxDescription()
+                ),
+                403,
+                $this->format
+            );
+            return false;
+        } elseif (
+            !is_null($this->location)
+            && mb_strlen($this->location) > 255) {
+                $this->clientError(
+                    _('Location is too long (max 255 chars).'),
+                    403,
+                    $this->format
+                );
+            return false;
+        }
+
+        if (!empty($this->aliasstring)) {
+            $this->aliases = array_map(
+                'common_canonical_nickname',
+                array_unique(preg_split('/[\s,]+/', $this->aliasstring))
+            );
+        } else {
+            $this->aliases = array();
+        }
+
+        if (count($this->aliases) > common_config('group', 'maxaliases')) {
+            $this->clientError(
+                sprintf(
+                    _('Too many aliases! Maximum %d.'),
+                    common_config('group', 'maxaliases')
+                ),
+                403,
+                $this->format
+            );
+            return false;
+        }
+
+        foreach ($this->aliases as $alias) {
+
+            $valid = Validate::string(
+                $alias, array(
+                    'min_length' => 1,
+                    'max_length' => 64,
+                    'format' => NICKNAME_FMT
+                )
+            );
+
+            if (!$valid) {
+                $this->clientError(
+                    sprintf(_('Invalid alias: "%s"'), $alias),
+                    403,
+                    $this->format
+                );
+                return false;
+            }
+            if ($this->groupNicknameExists($alias)) {
+                $this->clientError(
+                    sprintf(
+                        _('Alias "%s" already in use. Try another one.'),
+                        $alias
+                    ),
+                    403,
+                    $this->format
+                );
+                return false;
+            }
+
+            // XXX assumes alphanum nicknames
+
+            if (strcmp($alias, $this->nickname) == 0) {
+                $this->clientError(
+                    _('Alias can\'t be the same as nickname.'),
+                    403,
+                    $this->format
+                );
+                return false;
+            }
+        }
+
+        // Evarything looks OK
+
+        return true;
+    }
+
+    /**
+     * Check to see whether a nickname is already in use by a group
+     *
+     * @param String $nickname The nickname in question
+     *
+     * @return boolean true or false
+     */
+
+    function groupNicknameExists($nickname)
+    {
+        $group = User_group::staticGet('nickname', $nickname);
+
+        if (!empty($group)) {
+            return true;
+        }
+
+        $alias = Group_alias::staticGet('alias', $nickname);
+
+        if (!empty($alias)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
diff --git a/actions/apigroupismember.php b/actions/apigroupismember.php
new file mode 100644 (file)
index 0000000..a8a40a6
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Check to see whether a user a member of a group
+ *
+ * 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.
+ * @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';
+
+/**
+ * Returns whether a user is a member of a specified group.
+ *
+ * @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 ApiGroupIsMemberAction extends ApiBareAuthAction
+{
+    var $group   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->getTargetUser(null);
+        $this->group  = $this->getTargetGroup(null);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @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;
+        }
+
+        if (empty($this->group)) {
+            $this->clientError('Group not found!', 404, $this->format);
+            return false;
+        }
+
+        $is_member = $this->user->isMember($this->group);
+
+        switch($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->element('is_member', null, $is_member);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            $this->showJsonObjects(array('is_member' => $is_member));
+            $this->endDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                400,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
diff --git a/actions/apigroupjoin.php b/actions/apigroupjoin.php
new file mode 100644 (file)
index 0000000..071cd92
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Join a group via the 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
+ * @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.
+ * @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';
+
+/**
+ * Joins the authenticated user to the group speicified 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 ApiGroupJoinAction extends ApiAuthAction
+{
+    var $group   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user  = $this->auth_user;
+        $this->group = $this->getTargetGroup($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if (empty($this->group)) {
+            $this->clientError('Group not found!', 404, $this->format);
+            return false;
+        }
+
+        if ($this->user->isMember($this->group)) {
+            $this->clientError(
+                _('You are already a member of that group.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        if (Group_block::isBlocked($this->group, $this->user->getProfile())) {
+            $this->clientError(
+                _('You have been blocked from that group by the admin.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $member = new Group_member();
+
+        $member->group_id   = $this->group->id;
+        $member->profile_id = $this->user->id;
+        $member->created    = common_sql_now();
+
+        $result = $member->insert();
+
+        if (!$result) {
+            common_log_db_error($member, 'INSERT', __FILE__);
+            $this->serverError(
+                sprintf(
+                    _('Could not join user %s to group %s.'),
+                    $this->user->nickname,
+                    $this->group->nickname
+                )
+            );
+            return;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->show_single_xml_group($this->group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($this->group);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
diff --git a/actions/apigroupleave.php b/actions/apigroupleave.php
new file mode 100644 (file)
index 0000000..0d4bb9e
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Leave a group via the 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
+ * @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.
+ * @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';
+
+/**
+ * Removes the authenticated user from the group 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 ApiGroupLeaveAction extends ApiAuthAction
+{
+    var $group   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user  = $this->auth_user;
+        $this->group = $this->getTargetGroup($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if (empty($this->group)) {
+            $this->clientError('Group not found!', 404, $this->format);
+            return false;
+        }
+
+        $member = new Group_member();
+
+        $member->group_id   = $this->group->id;
+        $member->profile_id = $this->auth->id;
+
+        if (!$member->find(true)) {
+            $this->serverError(_('You are not a member of this group.'));
+            return;
+        }
+
+        $result = $member->delete();
+
+        if (!$result) {
+            common_log_db_error($member, 'INSERT', __FILE__);
+            $this->serverError(
+                sprintf(
+                    _('Could not remove user %s to group %s.'),
+                    $this->user->nickname,
+                    $this->$group->nickname
+                )
+            );
+            return;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->show_single_xml_group($this->group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($this->group);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
diff --git a/actions/apigrouplist.php b/actions/apigrouplist.php
new file mode 100644 (file)
index 0000000..c529c1e
--- /dev/null
@@ -0,0 +1,223 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Check to see whether a user a member of a group
+ *
+ * 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.
+ * @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';
+
+/**
+ * Returns whether a user is a member of a specified group.
+ *
+ * @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 ApiGroupListAction extends ApiBareAuthAction
+{
+    var $groups   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->getTargetUser($id);
+        $this->groups = $this->getGroups();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the user's groups
+     *
+     * @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;
+        }
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s's groups"), $this->user->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Groups";
+        $link       = common_local_url(
+            'usergroups',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _("Groups %s is a member of on %s."),
+            $this->user->nickname,
+            $sitename
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlGroups($this->groups);
+            break;
+        case 'rss':
+            $this->showRssGroups($this->groups, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() . 'api/statusnet/groups/list/' .
+                $this->user->id . '.atom';
+            $this->showAtomGroups(
+                $this->groups,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonGroups($this->groups);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+
+    }
+
+    /**
+     * Get groups
+     *
+     * @return array groups
+     */
+
+    function getGroups()
+    {
+        $groups = array();
+
+        $group = $this->user->getGroups(
+            ($this->page - 1) * $this->count,
+            $this->count,
+            $this->since_id,
+            $this->max_id,
+            $this->since
+        );
+
+        while ($group->fetch()) {
+            $groups[] = clone($group);
+        }
+
+        return $groups;
+    }
+
+    /**
+     * 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 group the user has joined
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+            return strtotime($this->groups[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this list of groups
+     *
+     * Returns an Etag based on the action name, language, user ID and
+     * timestamps of the first and last group the user has joined
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+
+            $last = count($this->groups) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->groups[0]->created),
+                      strtotime($this->groups[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apigrouplistall.php b/actions/apigrouplistall.php
new file mode 100644 (file)
index 0000000..89469f3
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the newest groups
+ *
+ * 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.
+ * @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/api.php';
+
+/**
+ * Returns of the lastest 20 groups for the site
+ *
+ * @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 ApiGroupListAllAction extends ApiAction
+{
+    var $groups   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->getTargetUser($id);
+        $this->groups = $this->getGroups();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the user's groups
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s groups"), $sitename);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Groups";
+        $link       = common_local_url('groups');
+        $subtitle   = sprintf(_("groups on %s"), $sitename);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlGroups($this->groups);
+            break;
+        case 'rss':
+            $this->showRssGroups($this->groups, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                'api/statusnet/groups/list_all.atom';
+            $this->showAtomGroups(
+                $this->groups,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonGroups($this->groups);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+
+    }
+
+    /**
+     * Get groups
+     *
+     * @return array groups
+     */
+
+    function getGroups()
+    {
+        $groups = array();
+
+        // XXX: Use the $page, $count, $max_id, $since_id, and $since parameters
+
+        $group = new User_group();
+        $group->orderBy('created DESC');
+        $group->find();
+
+        while ($group->fetch()) {
+            $groups[] = clone($group);
+        }
+
+        return $groups;
+    }
+
+    /**
+     * 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 site's latest group
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+            return strtotime($this->groups[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this list of groups
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the first and last group the user has joined
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+
+            $last = count($this->groups) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      strtotime($this->groups[0]->created),
+                      strtotime($this->groups[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apigroupmembership.php b/actions/apigroupmembership.php
new file mode 100644 (file)
index 0000000..b31e47b
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * List a group's 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    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.
+ * @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/api.php';
+
+/**
+ * List 20 newest members of the group specified by name or 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 ApiGroupMembershipAction extends ApiAction
+{
+    var $group    = null;
+    var $profiles = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->group    = $this->getTargetGroup($this->arg('id'));
+        $this->profiles = $this->getProfiles();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the members of the group
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        // XXX: RSS and Atom
+
+        switch($this->format) {
+        case 'xml':
+            $this->showTwitterXmlUsers($this->profiles);
+            break;
+        case 'json':
+            $this->showJsonUsers($this->profiles);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Fetch the members of a group
+     *
+     * @return array $profiles list of profiles
+     */
+
+    function getProfiles()
+    {
+        $profiles = array();
+
+        $profile = $this->group->getMembers(
+            ($this->page - 1) * $this->count,
+            $this->count,
+            $this->since_id,
+            $this->max_id,
+            $this->since
+        );
+
+        while ($profile->fetch()) {
+            $profiles[] = clone($profile);
+        }
+
+        return $profiles;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this list of profiles last modified?
+     *
+     * @return string datestamp of the lastest profile in the group
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+            return strtotime($this->profiles[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this list of groups
+     *
+     * Returns an Etag based on the action name, language
+     * the group id, and timestamps of the first and last
+     * user who has joined the group
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+
+            $last = count($this->profiles) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->group->id,
+                      strtotime($this->profiles[0]->created),
+                      strtotime($this->profiles[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apigroupshow.php b/actions/apigroupshow.php
new file mode 100644 (file)
index 0000000..2bdb22b
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show information about a group
+ *
+ * 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.
+ * @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/api.php';
+
+/**
+ * Outputs detailed information about the group 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 ApiGroupShowAction extends ApiAction
+{
+    var $group = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->group = $this->getTargetGroup($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->group)) {
+            $this->clientError(
+                'Group not found!',
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->show_single_xml_group($this->group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($this->group);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), 404, $this->format);
+            break;
+        }
+
+    }
+
+    /**
+     * When was this group last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->group)) {
+            return strtotime($this->group->modified);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this group
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the notice
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->group)) {
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->group->id,
+                      strtotime($this->group->modified))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apihelptest.php b/actions/apihelptest.php
new file mode 100644 (file)
index 0000000..e4ef55f
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Test that you can connect to the 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
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the string "ok" in the requested format with a 200 OK HTTP status code.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiHelpTestAction extends ApiAction
+{
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($this->format == 'xml') {
+            $this->initDocument('xml');
+            $this->element('ok', null, 'true');
+            $this->endDocument('xml');
+        } elseif ($this->format == 'json') {
+            $this->initDocument('json');
+            print '"ok"';
+            $this->endDocument('json');
+        } else {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+        }
+    }
+
+}
+
diff --git a/actions/apistatusesdestroy.php b/actions/apistatusesdestroy.php
new file mode 100644 (file)
index 0000000..8dc8793
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Destroy a notice through the 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
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Tom Blankenship <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Deletes one of the authenticating user's statuses (notices).
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Tom Blankenship <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiStatusesDestroyAction extends ApiAuthAction
+{
+    var $status                = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+        $this->notice_id = (int)$this->trimmed('id');
+
+        if (empty($notice_id)) {
+            $this->notice_id = (int)$this->arg('id');
+        }
+
+        $this->notice = Notice::staticGet((int)$this->notice_id);
+
+        return true;
+     }
+
+    /**
+     * Handle the request
+     *
+     * Delete the notice and all related replies
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+             $this->clientError(_('API method not found!'), $code = 404);
+             return;
+        }
+
+         if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
+             $this->clientError(_('This method requires a POST or DELETE.'),
+                 400, $this->format);
+             return;
+         }
+
+         if (empty($this->notice)) {
+             $this->clientError(_('No status found with that ID.'),
+                 404, $this->format);
+             return;
+         }
+
+         if ($this->user->id == $this->notice->profile_id) {
+             $replies = new Reply;
+             $replies->get('notice_id', $this->notice_id);
+             $replies->delete();
+             $this->notice->delete();
+
+             if ($this->format == 'xml') {
+                 $this->showSingleXmlStatus($this->notice);
+             } elseif ($this->format == 'json') {
+                 $this->show_single_json_status($this->notice);
+             }
+         } else {
+             $this->clientError(_('You may not delete another user\'s status.'),
+                 403, $this->format);
+         }
+
+        $this->showNotice();
+    }
+
+    /**
+     * Show the deleted notice
+     *
+     * @return void
+     */
+
+    function showNotice()
+    {
+        if (!empty($this->notice)) {
+            if ($this->format == 'xml') {
+                $this->showSingleXmlStatus($this->notice);
+            } elseif ($this->format == 'json') {
+                $this->show_single_json_status($this->notice);
+            }
+        }
+    }
+
+}
diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php
new file mode 100644 (file)
index 0000000..3be22ca
--- /dev/null
@@ -0,0 +1,206 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a notice (as a Twitter-style status)
+ *
+ * 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    Tom Blankenship <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the notice specified by id as a Twitter-style status and inline user
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Tom Blankenship <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiStatusesShowAction extends ApiAction
+{
+
+    var $notice_id = null;
+    var $notice    = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        // 'id' is an undocumented parameter in Twitter's API. Several
+        // clients make use of it, so we support it too.
+
+        // show.json?id=12345 takes precedence over /show/12345.json
+
+        $this->notice_id = (int)$this->trimmed('id');
+
+        if (empty($notice_id)) {
+            $this->notice_id = (int)$this->arg('id');
+        }
+
+        $this->notice = Notice::staticGet((int)$this->notice_id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the notice
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $this->showNotice();
+    }
+
+    /**
+     * Show the notice
+     *
+     * @return void
+     */
+
+    function showNotice()
+    {
+        if (!empty($this->notice)) {
+            if ($this->format == 'xml') {
+                $this->showSingleXmlStatus($this->notice);
+            } elseif ($this->format == 'json') {
+                $this->show_single_json_status($this->notice);
+            }
+        } else {
+
+            // XXX: Twitter just sets a 404 header and doens't bother
+            // to return an err msg
+
+            $deleted = Deleted_notice::staticGet($this->notice_id);
+
+            if (!empty($deleted)) {
+                $this->clientError(
+                    _('Status deleted.'),
+                    410,
+                    $this->format
+                );
+            } else {
+                $this->clientError(
+                    _('No status with that ID found.'),
+                    404,
+                    $this->format
+                );
+            }
+        }
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this notice last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notice)) {
+            return strtotime($this->notice->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this notice
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the notice
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notice)) {
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->notice->id,
+                      strtotime($this->notice->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php
new file mode 100644 (file)
index 0000000..0d71e15
--- /dev/null
@@ -0,0 +1,241 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Post a notice (update your status) through the 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
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Tom Blankenship <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Updates the authenticating user's status (posts a notice).
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Tom Blankenship <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiStatusesUpdateAction extends ApiAuthAction
+{
+    var $source                = null;
+    var $status                = null;
+    var $in_reply_to_status_id = null;
+
+    static $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return false;
+        }
+
+        $this->status = $this->trimmed('status');
+
+        if (empty($this->status)) {
+            $this->clientError(
+                'Client must provide a \'status\' parameter with a value.',
+                400,
+                $this->format
+            );
+
+            return false;
+        }
+
+        $this->source = $this->trimmed('source');
+
+        if (empty($this->source) || in_array($source, $this->reserved_sources)) {
+            $this->source = 'api';
+        }
+
+        $this->in_reply_to_status_id
+            = intval($this->trimmed('in_reply_to_status_id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Make a new notice for the update, save it, and show it
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400, $this->format
+            );
+            return;
+        }
+
+        $status_shortened = common_shorten_links($this->status);
+
+        if (Notice::contentTooLong($status_shortened)) {
+
+            // Note: Twitter truncates anything over 140, flags the status
+            // as "truncated."
+
+            $this->clientError(
+                sprintf(
+                    _('That\'s too long. Max notice size is %d chars.'),
+                    Notice::maxContent()
+                ),
+                406,
+                $this->format
+            );
+
+            return;
+        }
+
+        // Check for commands
+
+        $inter = new CommandInterpreter();
+        $cmd = $inter->handle_command($this->user, $status_shortened);
+
+        if ($cmd) {
+
+            if ($this->supported($cmd)) {
+                $cmd->execute(new Channel());
+            }
+
+            // Cmd not supported?  Twitter just returns your latest status.
+            // And, it returns your last status whether the cmd was successful
+            // or not!
+
+            $this->notice = $this->user->getCurrentNotice();
+
+        } else {
+
+            $reply_to = null;
+
+            if (!empty($this->in_reply_to_status_id)) {
+
+                // Check whether notice actually exists
+
+                $reply = Notice::staticGet($this->in_reply_to_status_id);
+
+                if ($reply) {
+                    $reply_to = $this->in_reply_to_status_id;
+                } else {
+                    $this->clientError(
+                        _('Not found'),
+                        $code = 404,
+                        $this->format
+                    );
+                    return;
+                }
+            }
+
+            $this->notice = Notice::saveNew(
+                $this->user->id,
+                html_entity_decode($this->status, ENT_NOQUOTES, 'UTF-8'),
+                $this->source,
+                1,
+                $reply_to
+            );
+
+            common_broadcast_notice($this->notice);
+        }
+
+        $this->showNotice();
+    }
+
+    /**
+     * Show the resulting notice
+     *
+     * @return void
+     */
+
+    function showNotice()
+    {
+        if (!empty($this->notice)) {
+            if ($this->format == 'xml') {
+                $this->showSingleXmlStatus($this->notice);
+            } elseif ($this->format == 'json') {
+                $this->show_single_json_status($this->notice);
+            }
+        }
+    }
+
+    /**
+     * Is this command supported when doing an update from the API?
+     *
+     * @param string $cmd the command to check for
+     *
+     * @return boolean true or false
+     */
+
+    function supported($cmd)
+    {
+        static $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand',
+            'FavCommand', 'OnCommand', 'OffCommand');
+
+        if (in_array(get_class($cmd), $cmdlist)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php
new file mode 100644 (file)
index 0000000..ed1d151
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Dump of configuration variables
+ *
+ * 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    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Gives a full dump of configuration variables for this instance
+ * of StatusNet, minus variables that may be security-sensitive (like
+ * passwords).
+ * URL: http://identi.ca/api/statusnet/config.(xml|json)
+ * Formats: xml, json
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiStatusnetConfigAction extends ApiAction
+{
+    var $keys = array(
+        'site' => array('name', 'server', 'theme', 'path', 'fancy', 'language',
+                        'email', 'broughtby', 'broughtbyurl', 'closed',
+                        'inviteonly', 'private'),
+        'license' => array('url', 'title', 'image'),
+        'nickname' => array('featured'),
+        'throttle' => array('enabled', 'count', 'timespan'),
+        'xmpp' => array('enabled', 'server', 'user')
+    );
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->elementStart('config');
+
+            // XXX: check that all sections and settings are legal XML elements
+
+            common_debug(var_export($this->keys, true));
+
+            foreach ($this->keys as $section => $settings) {
+                $this->elementStart($section);
+                foreach ($settings as $setting) {
+                    $value = common_config($section, $setting);
+                    if (is_array($value)) {
+                        $value = implode(',', $value);
+                    } else if ($value === false) {
+                        $value = 'false';
+                    } else if ($value === true) {
+                        $value = 'true';
+                    }
+                    $this->element($setting, null, $value);
+                }
+                $this->elementEnd($section);
+            }
+            $this->elementEnd('config');
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $result = array();
+            foreach ($this->keys as $section => $settings) {
+                $result[$section] = array();
+                foreach ($settings as $setting) {
+                    $result[$section][$setting]
+                        = common_config($section, $setting);
+                }
+            }
+            $this->initDocument('json');
+            $this->showJsonObjects($result);
+            $this->endDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
+
diff --git a/actions/apistatusnetversion.php b/actions/apistatusnetversion.php
new file mode 100644 (file)
index 0000000..e73ab98
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * A version stamp for the 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
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns a version number for this version of StatusNet, which
+ * should make things a bit easier for upgrades.
+ * URL: http://identi.ca/api/statusnet/version.(xml|json)
+ * Formats: xml, js
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiStatusnetVersionAction extends ApiAction
+{
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->element('version', null, STATUSNET_VERSION);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            print '"'.STATUSNET_VERSION.'"';
+            $this->endDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
+
diff --git a/actions/apisubscriptions.php b/actions/apisubscriptions.php
new file mode 100644 (file)
index 0000000..bc68dd1
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base class for showing subscription information in the 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
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * This class outputs a list of profiles as Twitter-style user and status objects.
+ * It is used by the API methods /api/statuses/(friends|followers). To support the
+ * social graph methods it also can output a simple list of IDs.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiSubscriptionsAction extends ApiBareAuthAction
+{
+    var $profiles = null;
+    var $tag      = null;
+    var $lite     = null;
+    var $ids_only = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->tag      = $this->arg('tag');
+
+        // Note: Twitter no longer supports 'lite'
+        $this->lite     = $this->arg('lite');
+
+        $this->ids_only = $this->arg('ids_only');
+
+        // If called as a social graph method, show 5000 per page, otherwise 100
+
+        $this->count    = isset($this->ids_only) ?
+            5000 : (int)$this->arg('count', 100);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return false;
+        }
+
+        $this->profiles = $this->getProfiles();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the profiles
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $this->initDocument($this->format);
+
+        if (isset($this->ids_only)) {
+            $this->showIds();
+        } else {
+            $this->showProfiles(isset($this->lite) ? false : true);
+        }
+
+        $this->endDocument($this->format);
+    }
+
+    /**
+     * Get profiles - should get overrrided
+     *
+     * @return array Profiles
+     */
+
+    function getProfiles()
+    {
+    }
+
+    /**
+     * 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 profile in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+            return strtotime($this->profiles[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this action
+     *
+     * Returns an Etag based on the action name, language, user ID, and
+     * timestamps of the first and last profiles in the subscriptions list
+     * There's also an indicator to show whether this action is being called
+     * as /api/statuses/(friends|followers) or /api/(friends|followers)/ids
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+
+            $last = count($this->profiles) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      isset($this->ids_only) ? 'IDs' : 'Profiles',
+                      strtotime($this->profiles[0]->created),
+                      strtotime($this->profiles[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+    /**
+     * Show the profiles as Twitter-style useres and statuses
+     *
+     * @param boolean $include_statuses Whether to include the latest status
+     *                                  with each user. Default true.
+     *
+     * @return void
+     */
+
+    function showProfiles($include_statuses = true)
+    {
+        switch ($this->format) {
+        case 'xml':
+            $this->elementStart('users', array('type' => 'array'));
+            foreach ($this->profiles as $profile) {
+                $this->showProfile(
+                    $profile,
+                    $this->format,
+                    null,
+                    $include_statuses
+                );
+            }
+            $this->elementEnd('users');
+            break;
+        case 'json':
+            $arrays = array();
+            foreach ($this->profiles as $profile) {
+                $arrays[] = $this->twitterUserArray(
+                    $profile,
+                    $include_statuses
+                );
+            }
+            print json_encode($arrays);
+            break;
+        default:
+            $this->clientError(_('Unsupported format.'));
+            break;
+        }
+    }
+
+    /**
+     * Show the IDs of the profiles only. 5000 per page. To support
+     * the 'social graph' methods: /api/(friends|followers)/ids
+     *
+     * @return void
+     */
+
+    function showIds()
+    {
+        switch ($this->format) {
+        case 'xml':
+            $this->elementStart('ids');
+            foreach ($this->profiles as $profile) {
+                $this->element('id', null, $profile->id);
+            }
+            $this->elementEnd('ids');
+            break;
+        case 'json':
+            $ids = array();
+            foreach ($this->profiles as $profile) {
+                $ids[] = (int)$profile->id;
+            }
+            print json_encode($ids);
+            break;
+        default:
+            $this->clientError(_('Unsupported format.'));
+            break;
+        }
+    }
+
+}
diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php
new file mode 100644 (file)
index 0000000..b8ae74f
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's favorite 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    Zach Copley <zach@status.net> * @copyright 2009 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);
+}
+
+require_once INSTALLDIR.'/lib/apibareauth.php';
+
+/**
+ * Returns the 20 most recent favorite notices for the authenticating user or user
+ * specified by the ID parameter in the requested format.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiTimelineFavoritesAction extends ApiBareAuthAction
+{
+    var $notices  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile = $this->user->getProfile();
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(
+            _('%s / Favorites from %s'),
+            $sitename,
+            $this->user->nickname
+        );
+
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Favorites:" . $this->user->id;
+        $link       = common_local_url(
+            'favorites',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('%s updates favorited by %s / %s.'),
+            $sitename,
+            $profile->getBestName(),
+            $this->user->nickname
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                ltrim($_SERVER['QUERY_STRING'], 'p=');
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link, $subtitle,
+                null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
+            $notice = $this->user->favoriteNotices(
+                ($this->page-1) * $this->count,
+                $this->count,
+                true
+            );
+        } else {
+            $notice = $this->user->favoriteNotices(
+                ($this->page-1) * $this->count,
+                $this->count,
+                false
+            );
+        }
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * 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, user 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->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php
new file mode 100644 (file)
index 0000000..1ea3586
--- /dev/null
@@ -0,0 +1,247 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the friends 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns the most recent notices (default 20) posted by the target user.
+ * This is the equivalent of 'You and friends' page accessed via Web.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelineFriendsAction extends ApiBareAuthAction
+{
+    var $notices  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile    = $this->user->getProfile();
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s and friends"), $this->user->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:FriendsTimeline:" . $this->user->id;
+        $link       = common_local_url(
+            'all', array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('Updates from %1$s and friends on %2$s!'),
+            $this->user->nickname, $sitename
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+
+            $target_id = $this->arg('id');
+
+            if (isset($target_id)) {
+                $selfuri = common_root_url() .
+                    'api/statuses/friends_timeline/' .
+                    $target_id . '.atom';
+            } else {
+                $selfuri = common_root_url() .
+                    'api/statuses/friends_timeline.atom';
+            }
+
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link,
+                $subtitle, null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
+            $notice = $this->user->noticeInbox(
+                ($this->page-1) * $this->count,
+                $this->count, $this->since_id,
+                $this->max_id, $this->since
+            );
+        } else {
+            $notice = $this->user->noticesWithFriends(
+                ($this->page-1) * $this->count,
+                $this->count, $this->since_id,
+                $this->max_id, $this->since
+            );
+        }
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * 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, user 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->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php
new file mode 100644 (file)
index 0000000..5d05429
--- /dev/null
@@ -0,0 +1,231 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a group'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.
+ * @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/api.php';
+
+/**
+ * Returns the most recent notices (default 20) posted to the group 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 ApiTimelineGroupAction extends ApiAction
+{
+
+    var $group   = null;
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->group   = $this->getTargetGroup($this->arg('id'));
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s timeline"), $this->group->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:GroupTimeline:" . $this->group->id;
+        $link       = common_local_url(
+            'showgroup',
+            array('nickname' => $this->group->nickname)
+        );
+        $subtitle   = sprintf(
+            _('Updates from %1$s on %2$s!'),
+            $this->group->nickname,
+            $sitename
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                'api/statusnet/groups/timeline/' .
+                    $this->group->nickname . '.atom';
+            $this->showAtomTimeline(
+                $this->notices,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                null,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = $this->group->getNotices(
+            ($this->page-1) * $this->count,
+            $this->count,
+            $this->since_id,
+            $this->max_id,
+            $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * 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, group 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->group->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php
new file mode 100644 (file)
index 0000000..fe5ff0f
--- /dev/null
@@ -0,0 +1,233 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show notices mentioning a user (@nickname)
+ *
+ * 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    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns the most recent (default 20) mentions (status containing @nickname)
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelineMentionsAction extends ApiBareAuthAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile = $this->user->getProfile();
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(
+            _('%1$s / Updates mentioning %2$s'),
+            $sitename, $this->user->nickname
+        );
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Mentions:" . $this->user->id;
+        $link       = common_local_url(
+            'replies',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('%1$s updates that reply to updates from %2$s / %3$s.'),
+            $sitename, $this->user->nickname, $profile->getBestName()
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                ltrim($_SERVER['QUERY_STRING'], 'p=');
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link, $subtitle,
+                null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = $this->user->getReplies(
+            ($this->page - 1) * $this->count, $this->count,
+            $this->since_id, $this->max_id, $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * 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, user 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->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php
new file mode 100644 (file)
index 0000000..58e2677
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the public 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the most recent notices (default 20) posted by everybody
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelinePublicAction extends ApiAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s public timeline"), $sitename);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:PublicTimeline";
+        $link       = common_root_url();
+        $subtitle   = sprintf(_("%s updates from everyone!"), $sitename);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link,
+                $subtitle, null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = Notice::publicStream(
+            ($this->page - 1) * $this->count, $this->count, $this->since_id,
+            $this->max_id, $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * 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, 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(),
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php
new file mode 100644 (file)
index 0000000..a274daa
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the latest notices for a given tag
+ *
+ * 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.
+ * @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/api.php';
+
+/**
+ * Returns the 20 most recent notices tagged by a given tag
+ *
+ * @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 ApiTimelineTagAction extends ApiAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->tag     = $this->arg('tag');
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("Notices tagged with %s"), $this->tag);
+        $link       = common_local_url(
+            'tag',
+            array('tag' => $this->tag)
+        );
+        $subtitle   = sprintf(
+            _('Updates tagged with %1$s on %2$s!'),
+            $this->tag,
+            $sitename
+        );
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:TagTimeline:".$tag;
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                'api/statusnet/tags/timeline/' .
+                    $this->tag . '.atom';
+            $this->showAtomTimeline(
+                $this->notices,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                null,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = Notice_tag::getStream(
+            $this->tag,
+            ($this->page - 1) * $this->count,
+            $this->count + 1
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * 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, 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->tag,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php
new file mode 100644 (file)
index 0000000..285735f
--- /dev/null
@@ -0,0 +1,248 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user'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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns the most recent notices (default 20) posted by the authenticating
+ * user. Another user's timeline can be requested via the id parameter. This
+ * is the API equivalent of the user profile web page.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelineUserAction extends ApiBareAuthAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile = $this->user->getProfile();
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s timeline"), $this->user->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:UserTimeline:" . $this->user->id;
+        $link       = common_local_url(
+            'showstream',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('Updates from %1$s on %2$s!'),
+            $this->user->nickname, $sitename
+        );
+
+        // FriendFeed's SUP protocol
+        // Also added RSS and Atom feeds
+
+        $suplink = common_local_url('sup', null, null, $this->user->id);
+        header('X-SUP-ID: ' . $suplink);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline(
+                $this->notices, $title, $link,
+                $subtitle, $suplink
+            );
+            break;
+        case 'atom':
+            if (isset($apidata['api_arg'])) {
+                $selfuri = common_root_url() .
+                    'api/statuses/user_timeline/' .
+                    $apidata['api_arg'] . '.atom';
+            } else {
+                $selfuri = common_root_url() .
+                    'api/statuses/user_timeline.atom';
+            }
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link,
+                $subtitle, $suplink, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = $this->user->getNotices(
+            ($this->page-1) * $this->count, $this->count,
+            $this->since_id, $this->max_id, $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * 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, user 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->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apiuserfollowers.php b/actions/apiuserfollowers.php
new file mode 100644 (file)
index 0000000..e8d92a7
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's followers (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
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Ouputs the authenticating user's followers (subscribers), each with
+ * current Twitter-style status inline.  They are ordered by the order
+ * in which they subscribed to the user, 100 at a time.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiUserFollowersAction extends ApiSubscriptionsAction
+{
+    /**
+     * Get the user's subscribers (followers) as an array of profiles
+     *
+     * @return array Profiles
+     */
+
+    function getProfiles()
+    {
+        $offset = ($this->page - 1) * $this->count;
+        $limit =  $this->count + 1;
+
+        $subs = null;
+
+        if (isset($this->tag)) {
+            $subs = $this->user->getTaggedSubscribers(
+                $this->tag, $offset, $limit
+            );
+        } else {
+            $subs = $this->user->getSubscribers(
+                $offset,
+                $limit
+            );
+        }
+
+        $profiles = array();
+
+        if (!empty($subs)) {
+            while ($subs->fetch()) {
+                $profiles[] = clone($subs);
+            }
+        }
+
+        return $profiles;
+    }
+
+}
diff --git a/actions/apiuserfriends.php b/actions/apiuserfriends.php
new file mode 100644 (file)
index 0000000..741a26e
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's friends (subscriptions)
+ *
+ * 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    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Ouputs the authenticating user's friends (subscriptions), each with
+ * current Twitter-style status inline.  They are ordered by the date
+ * in which the user subscribed to them, 100 at a time.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @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 ApiUserFriendsAction extends ApiSubscriptionsAction
+{
+    /**
+     * Get the user's subscriptions (friends) as an array of profiles
+     *
+     * @return array Profiles
+     */
+
+    function getProfiles()
+    {
+        $offset = ($this->page - 1) * $this->count;
+        $limit =  $this->count + 1;
+
+        $subs = null;
+
+        if (isset($this->tag)) {
+            $subs = $this->user->getTaggedSubscriptions(
+                $this->tag, $offset, $limit
+            );
+        } else {
+            $subs = $this->user->getSubscriptions(
+                $offset,
+                $limit
+            );
+        }
+
+        $profiles = array();
+
+        if (!empty($subs)) {
+            while ($subs->fetch()) {
+                $profiles[] = clone($subs);
+            }
+        }
+
+        return $profiles;
+    }
+
+}
diff --git a/actions/apiusershow.php b/actions/apiusershow.php
new file mode 100644 (file)
index 0000000..b3a939b
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's profile information
+ *
+ * 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    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Ouputs information for a user, specified by ID or screen name.
+ * The user's most recent status will be returned inline.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   mac65 <mac65@mac65.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 ApiUserShowAction extends ApiAction
+{
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $email = $this->arg('email');
+
+        // XXX: email field deprecated in Twitter's API
+
+        if (!empty($email)) {
+            $this->user = User::staticGet('email', $email);
+        } else {
+            $this->user = $this->getTargetUser($this->arg('id'));
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->user)) {
+            $this->clientError(_('Not found.'), 404, $this->format);
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $profile = $this->user->getProfile();
+
+        if (empty($profile)) {
+            $this->clientError(_('User has no profile.'));
+            return;
+        }
+
+        $twitter_user = $this->twitterUserArray($this->user->getProfile(), true);
+
+        if ($this->format == 'xml') {
+            $this->initDocument('xml');
+            $this->showTwitterXmlUser($twitter_user);
+            $this->endDocument('xml');
+        } elseif ($this->format == 'json') {
+            $this->initDocument('json');
+            $this->showJsonObjects($twitter_user);
+            $this->endDocument('json');
+        }
+
+    }
+
+}
diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php
deleted file mode 100644 (file)
index 93c8443..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class TwitapiaccountAction extends TwitterapiAction
-{
-    function verify_credentials($args, $apidata)
-    {
-        parent::handle($args);
-
-        switch ($apidata['content-type']) {
-        case 'xml':
-        case 'json':
-            $action_obj = new TwitapiusersAction();
-            $action_obj->prepare($args);
-            call_user_func(array($action_obj, 'show'), $args, $apidata);
-            break;
-        default:
-            header('Content-Type: text/html; charset=utf-8');
-            print 'Authorized';
-        }
-    }
-
-   function end_session($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function update_location($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $location = trim($this->arg('location'));
-
-        if (!is_null($location) && mb_strlen($location) > 255) {
-
-            // XXX: But Twitter just truncates and runs with it. -- Zach
-            $this->clientError(_('That\'s too long. Max notice size is 255 chars.'),
-                406, $apidate['content-type']);
-            return;
-        }
-
-        $user = $apidata['user']; // Always the auth user
-        $profile = $user->getProfile();
-
-        $orig_profile = clone($profile);
-        $profile->location = $location;
-
-        $result = $profile->update($orig_profile);
-
-        if (empty($result)) {
-            common_log_db_error($profile, 'UPDATE', __FILE__);
-            $this->serverError(_('Couldn\'t save profile.'));
-            return;
-        }
-
-        common_broadcast_profile($profile);
-        $type = $apidata['content-type'];
-
-        $this->init_document($type);
-        $this->show_profile($profile, $type);
-        $this->end_document($type);
-    }
-
-
-    function update_delivery_device($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    // We don't have a rate limit, but some clients check this method.
-    // It always returns the same thing: 100 hit left.
-    function rate_limit_status($args, $apidata)
-    {
-        parent::handle($args);
-
-        $type = $apidata['content-type'];
-        $this->init_document($type);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->elementStart('hash');
-            $this->element('remaining-hits', array('type' => 'integer'), 100);
-            $this->element('hourly-limit', array('type' => 'integer'), 100);
-            $this->element('reset-time', array('type' => 'datetime'), null);
-            $this->element('reset_time_in_seconds', array('type' => 'integer'), 0);
-            $this->elementEnd('hash');
-        } elseif ($apidata['content-type'] == 'json') {
-
-            $out = array('reset_time_in_seconds' => 0,
-                         'remaining_hits' => 100,
-                         'hourly_limit' => 100,
-                         'reset_time' => '');
-            print json_encode($out);
-        }
-
-        $this->end_document($type);
-    }
-}
diff --git a/actions/twitapiblocks.php b/actions/twitapiblocks.php
deleted file mode 100644 (file)
index ed17946..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class TwitapiblocksAction extends TwitterapiAction
-{
-
-    function create($args, $apidata)
-    {
-
-        parent::handle($args);
-
-        $blockee = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($blockee)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user']; // Always the auth user
-
-        if ($user->hasBlocked($blockee) || $user->block($blockee)) {
-            $type = $apidata['content-type'];
-            $this->init_document($type);
-            $this->show_profile($blockee, $type);
-            $this->end_document($type);
-        } else {
-            $this->serverError(_('Block user failed.'));
-        }
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-        $blockee = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($blockee)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];
-
-        if (!$user->hasBlocked($blockee) || $user->unblock($blockee)) {
-            $type = $apidata['content-type'];
-            $this->init_document($type);
-            $this->show_profile($blockee, $type);
-            $this->end_document($type);
-        } else {
-            $this->serverError(_('Unblock user failed.'));
-        }
-    }
-}
\ No newline at end of file
diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php
deleted file mode 100644 (file)
index 08b8f4e..0000000
+++ /dev/null
@@ -1,305 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class Twitapidirect_messagesAction extends TwitterapiAction
-{
-
-    function direct_messages($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->show_messages($args, $apidata, 'received');
-    }
-
-    function sent($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->show_messages($args, $apidata, 'sent');
-    }
-
-    function show_messages($args, $apidata, $type)
-    {
-        $user = $apidata['user']; // Always the auth user
-
-        $message  = new Message();
-        $title    = null;
-        $subtitle = null;
-        $link     = null;
-        $server   = common_root_url();
-
-        if ($type == 'received') {
-            $message->to_profile = $user->id;
-            $title = sprintf(_("Direct messages to %s"), $user->nickname);
-            $subtitle = sprintf(_("All the direct messages sent to %s"),
-                $user->nickname);
-            $link = $server . $user->nickname . '/inbox';
-        } else {
-            $message->from_profile = $user->id;
-            $title = _('Direct Messages You\'ve Sent');
-            $subtitle = sprintf(_("All the direct messages sent from %s"),
-                $user->nickname);
-            $link = $server . $user->nickname . '/outbox';
-        }
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        if ($max_id) {
-            $message->whereAdd("id <= $max_id");
-        }
-
-        if ($since_id) {
-            $message->whereAdd("id > $since_id");
-        }
-
-        if ($since) {
-            $d = date('Y-m-d H:i:s', $since);
-            $message->whereAdd("created > '$d'");
-        }
-
-        $message->orderBy('created DESC, id DESC');
-        $message->limit((($page-1)*$count), $count);
-        $message->find();
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_dmsgs($message);
-            break;
-        case 'rss':
-            $this->show_rss_dmsgs($message, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            $selfuri = common_root_url() . 'api/direct_messages';
-            $selfuri .= ($type == 'received') ? '.atom' : '/sent.atom';
-            $taguribase = common_config('integration', 'taguri');
-
-            if ($type == 'sent') {
-                $id = "tag:$taguribase:SentDirectMessages:" . $user->id;
-            } else {
-                $id = "tag:$taguribase:DirectMessages:" . $user->id;
-            }
-
-            $this->show_atom_dmsgs($message, $title, $link, $subtitle,
-                $selfuri, $id);
-            break;
-        case 'json':
-            $this->show_json_dmsgs($message);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    // had to change this from "new" to "create" to avoid PHP reserved word
-    function create($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];
-        $source = $this->trimmed('source'); // Not supported by Twitter.
-
-        $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
-        if (empty($source) || in_array($source, $reserved_sources)) {
-            $source = 'api';
-        }
-
-        $content = $this->trimmed('text');
-
-        if (empty($content)) {
-            $this->clientError(_('No message text!'),
-                $code = 406, $apidata['content-type']);
-        } else {
-            $content_shortened = common_shorten_links($content);
-            if (Message::contentTooLong($content_shortened)) {
-                $this->clientError(sprintf(_('That\'s too long. Max message size is %d chars.'),
-                                           Message::maxContent()),
-                                   $code = 406, $apidata['content-type']);
-                return;
-            }
-        }
-
-        $other = $this->get_user($this->trimmed('user'));
-
-        if (empty($other)) {
-            $this->clientError(_('Recipient user not found.'),
-                $code = 403, $apidata['content-type']);
-            return;
-        } else if (!$user->mutuallySubscribed($other)) {
-            $this->clientError(_('Can\'t send direct messages to users who aren\'t your friend.'),
-                $code = 403, $apidata['content-type']);
-            return;
-        } else if ($user->id == $other->id) {
-            // Sending msgs to yourself is allowed by Twitter
-            $this->clientError(_('Don\'t send a message to yourself; just say it to yourself quietly instead.'),
-                $code = 403, $apidata['content-type']);
-            return;
-        }
-
-        $message = Message::saveNew($user->id, $other->id,
-            html_entity_decode($content, ENT_NOQUOTES, 'UTF-8'), $source);
-
-        if (is_string($message)) {
-            $this->serverError($message);
-            return;
-        }
-
-        $this->notify($user, $other, $message);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_dmsg($message);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_dmsg($message);
-        }
-
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function show_xml_dmsgs($message)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('direct-messages', array('type' => 'array'));
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $twitter_dm = $this->twitter_dmsg_array($m);
-                $this->show_twitter_xml_dmsg($twitter_dm);
-            }
-        } else {
-            while ($message->fetch()) {
-                $twitter_dm = $this->twitter_dmsg_array($message);
-                $this->show_twitter_xml_dmsg($twitter_dm);
-            }
-        }
-
-        $this->elementEnd('direct-messages');
-        $this->end_document('xml');
-
-    }
-
-    function show_json_dmsgs($message)
-    {
-
-        $this->init_document('json');
-
-        $dmsgs = array();
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $twitter_dm = $this->twitter_dmsg_array($m);
-                array_push($dmsgs, $twitter_dm);
-            }
-        } else {
-            while ($message->fetch()) {
-                $twitter_dm = $this->twitter_dmsg_array($message);
-                array_push($dmsgs, $twitter_dm);
-            }
-        }
-
-        $this->show_json_objects($dmsgs);
-        $this->end_document('json');
-
-    }
-
-    function show_rss_dmsgs($message, $title, $link, $subtitle)
-    {
-
-        $this->init_document('rss');
-
-        $this->elementStart('channel');
-        $this->element('title', null, $title);
-
-        $this->element('link', null, $link);
-        $this->element('description', null, $subtitle);
-        $this->element('language', null, 'en-us');
-        $this->element('ttl', null, '40');
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $entry = $this->twitter_rss_dmsg_array($m);
-                $this->show_twitter_rss_item($entry);
-            }
-        } else {
-            while ($message->fetch()) {
-                $entry = $this->twitter_rss_dmsg_array($message);
-                $this->show_twitter_rss_item($entry);
-            }
-        }
-
-        $this->elementEnd('channel');
-        $this->end_twitter_rss();
-
-    }
-
-    function show_atom_dmsgs($message, $title, $link, $subtitle, $selfuri, $id)
-    {
-
-        $this->init_document('atom');
-
-        $this->element('title', null, $title);
-        $this->element('id', null, $id);
-        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
-        $this->element('link', array('href' => $selfuri, 'rel' => 'self',
-            'type' => 'application/atom+xml'), null);
-        $this->element('updated', null, common_date_iso8601('now'));
-        $this->element('subtitle', null, $subtitle);
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $entry = $this->twitter_rss_dmsg_array($m);
-                $this->show_twitter_atom_entry($entry);
-            }
-        } else {
-            while ($message->fetch()) {
-                $entry = $this->twitter_rss_dmsg_array($message);
-                $this->show_twitter_atom_entry($entry);
-            }
-        }
-
-        $this->end_document('atom');
-    }
-
-    // swiped from MessageAction. Should it be place in util.php?
-    function notify($from, $to, $message)
-    {
-        mail_notify_message($message, $from, $to);
-        # XXX: Jabber, SMS notifications... probably queued
-    }
-
-}
diff --git a/actions/twitapifavorites.php b/actions/twitapifavorites.php
deleted file mode 100644 (file)
index f8943fe..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class TwitapifavoritesAction extends TwitterapiAction
-{
-
-    function favorites($args, $apidata)
-    {
-        parent::handle($args);
-
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_status($notice);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_status($notice);
-        }
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_('%s / Favorites from %s'), $sitename,
-            $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:Favorites:".$user->id;
-        $link       = common_local_url('favorites',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('%s updates favorited by %s / %s.'), $sitename,
-            $profile->getBestName(), $user->nickname);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        if (!empty($this->auth_user) && $this->auth_user->id == $user->id) {
-            $notice = $user->favoriteNotices(($page-1)*$count, $count, true);
-        } else {
-            $notice = $user->favoriteNotices(($page-1)*$count, $count, false);
-        }
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            if (isset($apidata['api_arg'])) {
-                 $selfuri = $selfuri = common_root_url() .
-                     'api/favorites/' . $apidata['api_arg'] . '.atom';
-            } else {
-                 $selfuri = $selfuri = common_root_url() .
-                  'api/favorites.atom';
-            }
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function create($args, $apidata)
-    {
-        parent::handle($args);
-
-        // Check for RESTfulness
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user      = $apidata['user']; // Always the auth user
-        $notice_id = $apidata['api_arg'];
-        $notice    = Notice::staticGet($notice_id);
-
-        if (empty($notice)) {
-            $this->clientError(_('No status found with that ID.'),
-                404, $apidata['content-type']);
-            return;
-        }
-
-        // XXX: Twitter lets you fave things repeatedly via api.
-        if ($user->hasFave($notice)) {
-            $this->clientError(_('This status is already a favorite!'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $fave = Fave::addNew($user, $notice);
-
-        if (empty($fave)) {
-            $this->clientError(_('Could not create favorite.'));
-            return;
-        }
-
-        $this->notify($fave, $notice, $user);
-        $user->blowFavesCache();
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_status($notice);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_status($notice);
-        }
-
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-
-        // Check for RESTfulness
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user      = $apidata['user']; // Always the auth user
-        $notice_id = $apidata['api_arg'];
-        $notice    = Notice::staticGet($notice_id);
-
-        if (empty($notice)) {
-            $this->clientError(_('No status found with that ID.'),
-                404, $apidata['content-type']);
-            return;
-        }
-
-        $fave            = new Fave();
-        $fave->user_id   = $this->id;
-        $fave->notice_id = $notice->id;
-
-        if (!$fave->find(true)) {
-            $this->clientError(_('That status is not a favorite!'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $result = $fave->delete();
-
-        if (!$result) {
-            common_log_db_error($fave, 'DELETE', __FILE__);
-            $this->clientError(_('Could not delete favorite.'), 404);
-            return;
-        }
-
-        $user->blowFavesCache();
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_status($notice);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_status($notice);
-        }
-
-    }
-
-    // XXX: these two funcs swiped from faves.
-    // Maybe put in util.php, or some common base class?
-
-    function notify($fave, $notice, $user)
-    {
-        $other = User::staticGet('id', $notice->profile_id);
-        if ($other && $other->id != $user->id) {
-            if ($other->email && $other->emailnotifyfav) {
-                mail_notify_fave($other, $user, $notice);
-            }
-            # XXX: notify by IM
-            # XXX: notify by SMS
-        }
-    }
-}
diff --git a/actions/twitapifriendships.php b/actions/twitapifriendships.php
deleted file mode 100644 (file)
index eea8945..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class TwitapifriendshipsAction extends TwitterapiAction
-{
-
-    function create($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $id    = $apidata['api_arg'];
-        $other = $this->get_user($id);
-
-        if (empty($other)) {
-            $this->clientError(_('Could not follow user: User not found.'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];
-
-        if ($user->isSubscribed($other)) {
-            $errmsg = sprintf(_('Could not follow user: %s is already on your list.'),
-                $other->nickname);
-            $this->clientError($errmsg, 403, $apidata['content-type']);
-            return;
-        }
-
-        $sub = new Subscription();
-
-        $sub->query('BEGIN');
-
-        $sub->subscriber = $user->id;
-        $sub->subscribed = $other->id;
-        $sub->created = DB_DataObject_Cast::dateTime(); # current time
-
-        $result = $sub->insert();
-
-        if (empty($result)) {
-            $errmsg = sprintf(_('Could not follow user: %s is already on your list.'),
-                $other->nickname);
-            $this->clientError($errmsg, 400, $apidata['content-type']);
-            return;
-        }
-
-        $sub->query('COMMIT');
-
-        mail_subscribe_notify($other, $user);
-
-        $type = $apidata['content-type'];
-        $this->init_document($type);
-        $this->show_profile($other, $type);
-        $this->end_document($type);
-
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $id = $apidata['api_arg'];
-
-        # We can't subscribe to a remote person, but we can unsub
-
-        $other = $this->get_profile($id);
-        $user = $apidata['user']; // Alwyas the auth user
-
-       if ($user->id == $other->id) {
-           $this->clientError(_("You cannot unfollow yourself!"),
-                              403, $apidata['content-type']);
-           return;
-       }
-
-        $sub = new Subscription();
-        $sub->subscriber = $user->id;
-        $sub->subscribed = $other->id;
-
-        if ($sub->find(true)) {
-            $sub->query('BEGIN');
-            $sub->delete();
-            $sub->query('COMMIT');
-        } else {
-            $this->clientError(_('You are not friends with the specified user.'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $type = $apidata['content-type'];
-        $this->init_document($type);
-        $this->show_profile($other, $type);
-        $this->end_document($type);
-
-    }
-
-    function exists($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user_a_id = $this->trimmed('user_a');
-        $user_b_id = $this->trimmed('user_b');
-
-        $user_a = $this->get_user($user_a_id);
-        $user_b = $this->get_user($user_b_id);
-
-        if (empty($user_a) || empty($user_b)) {
-            $this->clientError(_('Two user ids or screen_names must be supplied.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $result = $user_a->isSubscribed($user_b);
-
-        switch ($apidata['content-type']) {
-         case 'xml':
-            $this->init_document('xml');
-            $this->element('friends', null, $result);
-            $this->end_document('xml');
-            break;
-         case 'json':
-            $this->init_document('json');
-            print json_encode($result);
-            $this->end_document('json');
-            break;
-         default:
-            break;
-        }
-
-    }
-
-    function show($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $source_id          = (int)$this->trimmed('source_id');
-        $source_screen_name = $this->trimmed('source_screen_name');
-
-        // If the source is not specified for an unauthenticated request,
-        // the method will return an HTTP 403.
-
-        if (empty($source_id) && empty($source_screen_name)) {
-            if (empty($apidata['user'])) {
-                $this->clientError(_('Could not determine source user.'),
-                        $code = 403);
-                return;
-            }
-        }
-
-        $source = null;
-
-        if (!empty($source_id)) {
-            $source = User::staticGet($source_id);
-        } elseif (!empty($source_screen_name)) {
-            $source = User::staticGet('nickname', $source_screen_name);
-        } else {
-            $source = $apidata['user'];
-        }
-
-        // If a source or target is specified but does not exist,
-        // the method will return an HTTP 404.
-
-        if (empty($source)) {
-            $this->clientError(_('Could not determine source user.'),
-                $code = 404);
-            return;
-        }
-
-        $target_id          = (int)$this->trimmed('target_id');
-        $target_screen_name = $this->trimmed('target_screen_name');
-
-        $target = null;
-
-        if (!empty($target_id)) {
-            $target = User::staticGet($target_id);
-        } elseif (!empty($target_screen_name)) {
-            $target = User::staticGet('nickname', $target_screen_name);
-        } else {
-            $this->clientError(_('Target user not specified.'),
-                $code = 403);
-            return;
-        }
-
-        if (empty($target)) {
-            $this->clientError(_('Could not find target user.'),
-                $code = 404);
-            return;
-        }
-
-        $result = $this->twitter_relationship_array($source, $target);
-
-        switch ($apidata['content-type']) {
-        case 'xml':
-            $this->init_document('xml');
-            $this->show_twitter_xml_relationship($result[relationship]);
-            $this->end_document('xml');
-            break;
-        case 'json':
-            $this->init_document('json');
-            print json_encode($result);
-            $this->end_document('json');
-            break;
-        default:
-            break;
-        }
-    }
-
-}
diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php
deleted file mode 100644 (file)
index a294858..0000000
+++ /dev/null
@@ -1,596 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * StatusNet extensions to the Twitter-like API for groups
- *
- * 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  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 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') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/twitterapi.php';
-
-/**
- * Group-specific API methods
- *
- * This class handles StatusNet group API methods.
- *
- * @category  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 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/
- */
-
- class TwitapigroupsAction extends TwitterapiAction
- {
-
-     function list_groups($args, $apidata)
-     {
-         parent::handle($args);
-         
-         common_debug("in groups api action");
-         
-         $this->auth_user = $apidata['user'];
-         $user = $this->get_user($apidata['api_arg'], $apidata);
-
-         if (empty($user)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-         $group = $user->getGroups(($page-1)*$count,
-             $count, $since_id, $max_id, $since);
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("%s's groups"), $user->nickname);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:Groups";
-         $link       = common_root_url();
-         $subtitle   = sprintf(_("groups %s is a member of on %s"), $user->nickname, $sitename);
-
-         switch($apidata['content-type']) {
-         case 'xml':
-             $this->show_xml_groups($group);
-             break;
-         case 'rss':
-             $this->show_rss_groups($group, $title, $link, $subtitle);
-             break;
-         case 'atom':
-             $selfuri = common_root_url() . 'api/statusnet/groups/list/' . $user->id . '.atom';
-             $this->show_atom_groups($group, $title, $id, $link,
-                 $subtitle, $selfuri);
-             break;
-         case 'json':
-             $this->show_json_groups($group);
-             break;
-         default:
-             $this->clientError(_('API method not found!'), $code = 404);
-             break;
-         }
-     }
-
-     function list_all($args, $apidata)
-     {
-         parent::handle($args);
-         
-         common_debug("in groups api action");
-         
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         /*     TODO:
-         Use the $page, $count, $max_id, $since_id, and $since parameters
-         */
-         $group = new User_group();
-         $group->orderBy('created DESC');
-         $group->find();
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("%s groups"), $sitename);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:Groups";
-         $link       = common_root_url();
-         $subtitle   = sprintf(_("groups on %s"), $sitename);
-
-         switch($apidata['content-type']) {
-         case 'xml':
-             $this->show_xml_groups($group);
-             break;
-         case 'rss':
-             $this->show_rss_groups($group, $title, $link, $subtitle);
-             break;
-         case 'atom':
-             $selfuri = common_root_url() . 'api/statusnet/groups/list_all.atom';
-             $this->show_atom_groups($group, $title, $id, $link,
-                 $subtitle, $selfuri);
-             break;
-         case 'json':
-             $this->show_json_groups($group);
-             break;
-         default:
-             $this->clientError(_('API method not found!'), $code = 404);
-             break;
-         }
-     }
-
-     function show($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_single_xml_group($group);
-             break;
-          case 'json':
-             $this->show_single_json_group($group);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function timeline($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("%s timeline"), $group->nickname);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:GroupTimeline:".$group->id;
-         $link       = common_local_url('showgroup',
-             array('nickname' => $group->nickname));
-         $subtitle   = sprintf(_('Updates from %1$s on %2$s!'),
-             $group->nickname, $sitename);
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         $notice = $group->getNotices(($page-1)*$count,
-             $count, $since_id, $max_id, $since);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_xml_timeline($notice);
-             break;
-          case 'rss':
-             $this->show_rss_timeline($notice, $title, $link, $subtitle);
-             break;
-          case 'atom':
-             if (isset($apidata['api_arg'])) {
-                 $selfuri = common_root_url() .
-                     'api/statusnet/groups/timeline/' .
-                         $apidata['api_arg'] . '.atom';
-             } else {
-                 $selfuri = common_root_url() .
-                  'api/statusnet/groups/timeline.atom';
-             }
-             $this->show_atom_timeline($notice, $title, $id, $link,
-                 $subtitle, null, $selfuri);
-             break;
-          case 'json':
-             $this->show_json_timeline($notice);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function membership($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-         
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("Members of %s group"), $group->nickname);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:GroupMembership:".$group->id;
-         $link       = common_local_url('showgroup',
-             array('nickname' => $group->nickname));
-         $subtitle   = sprintf(_('Members of %1$s on %2$s'),
-             $group->nickname, $sitename);
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         $member = $group->getMembers(($page-1)*$count,
-             $count, $since_id, $max_id, $since);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_twitter_xml_users($member);
-             break;
-          //TODO implement the RSS and ATOM content types
-          /*case 'rss':
-             $this->show_rss_users($member, $title, $link, $subtitle);
-             break;*/
-          /*case 'atom':
-             if (isset($apidata['api_arg'])) {
-                 $selfuri = common_root_url() .
-                     'api/statusnet/groups/membership/' .
-                         $apidata['api_arg'] . '.atom';
-             } else {
-                 $selfuri = common_root_url() .
-                  'api/statusnet/groups/membership.atom';
-             }
-             $this->show_atom_users($member, $title, $id, $link,
-                 $subtitle, null, $selfuri);
-             break;*/
-          case 'json':
-             $this->show_json_users($member);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function join($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return false;
-         }
-
-         if($this->auth_user->isMember($group)){
-            $this->clientError(_('You are already a member of that group'), $code = 403);
-            return false;
-         }
-
-         if (Group_block::isBlocked($group, $this->auth_user->getProfile())) {
-            $this->clientError(_('You have been blocked from that group by the admin.'), 403);
-            return false;
-         }
-
-         $member = new Group_member();
-
-         $member->group_id   = $group->id;
-         $member->profile_id = $this->auth_user->id;
-         $member->created    = common_sql_now();
-
-         $result = $member->insert();
-
-         if (!$result) {
-            common_log_db_error($member, 'INSERT', __FILE__);
-            $this->serverError(sprintf(_('Could not join user %s to group %s'),
-                                       $this->auth_user->nickname, $group->nickname));
-         }
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_single_xml_group($group);
-             break;
-          case 'json':
-             $this->show_single_json_group($group);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function leave($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return false;
-         }
-
-         if(! $this->auth_user->isMember($group)){
-            $this->clientError(_('You are not a member of that group'), $code = 403);
-            return false;
-         }
-
-         $member = new Group_member();
-
-         $member->group_id   = $group->id;
-         $member->profile_id = $this->auth_user->id;
-
-         if (!$member->find(true)) {
-            $this->serverError(_('Could not find membership record.'));
-            return;
-         }
-
-         $result = $member->delete();
-
-         if (!$result) {
-            common_log_db_error($member, 'INSERT', __FILE__);
-            $this->serverError(sprintf(_('Could not remove user %s to group %s'),
-                                       $this->auth_user->nickname, $group->nickname));
-         }
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_single_xml_group($group);
-             break;
-          case 'json':
-             $this->show_single_json_group($group);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function is_member($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = User_group::staticGet($args['group_id']);
-         if(! $group){
-            $this->clientError(_('Group not found'), $code = 500);
-         }
-         $user = User::staticGet('id', $args['user_id']);
-         if(! $user){
-            $this->clientError(_('User not found'), $code = 500);
-         }
-         
-         $is_member=$user->isMember($group);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->init_document('xml');
-             $this->element('is_member', null, $is_member);
-             $this->end_document('xml');
-             break;
-          case 'json':
-             $this->init_document('json');
-             $this->show_json_objects(array('is_member'=>$is_member));
-             $this->end_document('json');
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function create($args, $apidata)
-     {
-        parent::handle($args);
-
-        common_debug("in groups api action");
-        if (!common_config('inboxes','enabled')) {
-           $this->serverError(_('Inboxes must be enabled for groups to work'));
-           return false;
-        }
-
-        $this->auth_user = $apidata['user'];
-
-        $nickname    = $args['nickname'];
-        $fullname    = $args['full_name'];
-        $homepage    = $args['homepage'];
-        $description = $args['description'];
-        $location    = $args['location'];
-        $aliasstring = $args['aliases'];
-
-        if (!Validate::string($nickname, array('min_length' => 1,
-                                               'max_length' => 64,
-                                               'format' => NICKNAME_FMT))) {
-            $this->clientError(_('Nickname must have only lowercase letters '.
-                              'and numbers and no spaces.'), $code=403);
-            return;
-        } else if ($this->groupNicknameExists($nickname)) {
-            $this->clientError(_('Nickname already in use. Try another one.'), $code=403);
-            return;
-        } else if (!User_group::allowedNickname($nickname)) {
-            $this->clientError(_('Not a valid nickname.'), $code=403);
-            return;
-        } else if (!is_null($homepage) && (strlen($homepage) > 0) &&
-                   !Validate::uri($homepage,
-                                  array('allowed_schemes' =>
-                                        array('http', 'https')))) {
-            $this->clientError(_('Homepage is not a valid URL.'), $code=403);
-            return;
-        } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
-            $this->clientError(_('Full name is too long (max 255 chars).'), $code=403);
-            return;
-        } else if (User_group::descriptionTooLong($description)) {
-            $this->clientError(sprintf(_('description is too long (max %d chars).'), User_group::maxDescription()), $code=403);
-            return;
-        } else if (!is_null($location) && mb_strlen($location) > 255) {
-            $this->clientError(_('Location is too long (max 255 chars).'), $code=403);
-            return;
-        }
-
-        if (!empty($aliasstring)) {
-            $aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
-        } else {
-            $aliases = array();
-        }
-
-        if (count($aliases) > common_config('group', 'maxaliases')) {
-            $this->clientError(sprintf(_('Too many aliases! Maximum %d.'),
-                                    common_config('group', 'maxaliases')), $code=403);
-            return;
-        }
-
-        foreach ($aliases as $alias) {
-            if (!Validate::string($alias, array('min_length' => 1,
-                                                'max_length' => 64,
-                                                'format' => NICKNAME_FMT))) {
-                $this->clientError(sprintf(_('Invalid alias: "%s"'), $alias), $code=403);
-                return;
-            }
-            if ($this->groupNicknameExists($alias)) {
-                $this->clientError(sprintf(_('Alias "%s" already in use. Try another one.'),
-                                        $alias), $code=403);
-                return;
-            }
-            // XXX assumes alphanum nicknames
-            if (strcmp($alias, $nickname) == 0) {
-                $this->clientError(_('Alias can\'t be the same as nickname.'), $code=403);
-                return;
-            }
-        }
-
-        $group = new User_group();
-
-        $group->query('BEGIN');
-
-        $group->nickname    = $nickname;
-        $group->fullname    = $fullname;
-        $group->homepage    = $homepage;
-        $group->description = $description;
-        $group->location    = $location;
-        $group->created     = common_sql_now();
-
-        $result = $group->insert();
-
-        if (!$result) {
-            common_log_db_error($group, 'INSERT', __FILE__);
-            $this->serverError(_('Could not create group.'));
-        }
-
-        $result = $group->setAliases($aliases);
-
-        if (!$result) {
-            $this->serverError(_('Could not create aliases.'));
-        }
-
-        $member = new Group_member();
-
-        $member->group_id   = $group->id;
-        $member->profile_id = $this->auth_user->id;
-        $member->is_admin   = 1;
-        $member->created    = $group->created;
-
-        $result = $member->insert();
-
-        if (!$result) {
-            common_log_db_error($member, 'INSERT', __FILE__);
-            $this->serverError(_('Could not set group membership.'));
-        }
-
-        $group->query('COMMIT');
-
-        switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_single_xml_group($group);
-             break;
-          case 'json':
-             $this->show_single_json_group($group);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function update($args, $apidata)
-     {
-        die("todo");
-     }
-
-     function update_group_logo($args, $apidata)
-     {
-        die("todo");
-     }
-
-     function destroy($args, $apidata)
-     {
-        die("todo");
-     }
-
-     function tag($args, $apidata)
-     {
-        die("todo");
-     }
-
-     function groupNicknameExists($nickname)
-     {
-        $group = User_group::staticGet('nickname', $nickname);
-
-        if (!empty($group)) {
-            return true;
-        }
-
-        $alias = Group_alias::staticGet('alias', $nickname);
-
-        if (!empty($alias)) {
-            return true;
-        }
-
-        return false;
-     }
-}
diff --git a/actions/twitapihelp.php b/actions/twitapihelp.php
deleted file mode 100644 (file)
index 8138162..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class TwitapihelpAction extends TwitterapiAction
-{
-
-    /* Returns the string "ok" in the requested format with a 200 OK HTTP status code.
-     * URL:http://identi.ca/api/help/test.format
-     * Formats: xml, json
-     */
-    function test($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->init_document('xml');
-            $this->element('ok', null, 'true');
-            $this->end_document('xml');
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->init_document('json');
-            print '"ok"';
-            $this->end_document('json');
-        } else {
-            $this->clientError(_('API method not found!'), $code=404);
-        }
-
-    }
-
-    function downtime_schedule($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-}
\ No newline at end of file
diff --git a/actions/twitapinotifications.php b/actions/twitapinotifications.php
deleted file mode 100644 (file)
index 0653e69..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-# This naming convention looks real sick
-class TwitapinotificationsAction extends TwitterapiAction
-{
-
-    function follow($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function leave($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-}
\ No newline at end of file
index 2f587d604ffe5016258336183ffdbded64f61af9..7d618c471fe00dbc946a682b18383799176b73b4 100644 (file)
@@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/twitterapi.php';
+require_once INSTALLDIR.'/lib/api.php';
 
 /**
  * Action for outputting search results in Twitter compatible Atom
@@ -46,10 +46,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://status.net/
  *
- * @see      TwitterapiAction
+ * @see      ApiAction
  */
 
-class TwitapisearchatomAction extends TwitterapiAction
+class TwitapisearchatomAction extends ApiAction
 {
 
     var $cnt;
@@ -340,7 +340,7 @@ class TwitapisearchatomAction extends TwitterapiAction
         // TODO: Here is where we'd put in a link to an atom feed for threads
 
         $this->element("twitter:source", null,
-            htmlentities($this->source_link($notice->source)));
+            htmlentities($this->sourceLink($notice->source)));
 
         $this->elementStart('author');
 
index c628ee624a2a3498d7ff4a24cf329f6663a127f8..c7fa741a069477e797a89c55fb539f829c6b290f 100644 (file)
@@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/twitterapi.php';
+require_once INSTALLDIR.'/lib/api.php';
 require_once INSTALLDIR.'/lib/jsonsearchresultslist.php';
 
 /**
@@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/jsonsearchresultslist.php';
  * @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/
- * @see      TwitterapiAction
+ * @see      ApiAction
  */
 
-class TwitapisearchjsonAction extends TwitterapiAction
+class TwitapisearchjsonAction extends ApiAction
 {
     var $query;
     var $lang;
@@ -134,9 +134,9 @@ class TwitapisearchjsonAction extends TwitterapiAction
 
         $results = new JSONSearchResultsList($notice, $q, $this->rpp, $this->page);
 
-        $this->init_document('json');
+        $this->initDocument('json');
         $results->show();
-        $this->end_document('json');
+        $this->endDocument('json');
     }
 
     /**
diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php
deleted file mode 100644 (file)
index c589bd9..0000000
+++ /dev/null
@@ -1,606 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class TwitapistatusesAction extends TwitterapiAction
-{
-
-    function public_timeline($args, $apidata)
-    {
-        // XXX: To really live up to the spec we need to build a list
-        // of notices by users who have custom avatars, so fix this SQL -- Zach
-
-        parent::handle($args);
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_("%s public timeline"), $sitename);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:PublicTimeline";
-        $link       = common_root_url();
-        $subtitle   = sprintf(_("%s updates from everyone!"), $sitename);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        $notice = Notice::publicStream(($page-1)*$count, $count, $since_id,
-            $max_id, $since);
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-            break;
-        }
-
-    }
-
-    function friends_timeline($args, $apidata)
-    {
-        parent::handle($args);
-
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-             $this->clientError(_('No such user!'), 404,
-             $apidata['content-type']);
-            return;
-        }
-
-        $profile    = $user->getProfile();
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_("%s and friends"), $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:FriendsTimeline:" . $user->id;
-        $link       = common_local_url('all',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('Updates from %1$s and friends on %2$s!'),
-            $user->nickname, $sitename);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        if (!empty($this->auth_user) && $this->auth_user->id == $user->id) {
-            $notice = $user->noticeInbox(($page-1)*$count,
-                $count, $since_id, $max_id, $since);
-        } else {
-            $notice = $user->noticesWithFriends(($page-1)*$count,
-                $count, $since_id, $max_id, $since);
-        }
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            if (isset($apidata['api_arg'])) {
-                $selfuri = common_root_url() .
-                    'api/statuses/friends_timeline/' .
-                        $apidata['api_arg'] . '.atom';
-            } else {
-                $selfuri = common_root_url() .
-                    'api/statuses/friends_timeline.atom';
-            }
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function home_timeline($args, $apidata)
-    {
-        call_user_func(array($this, 'friends_timeline'), $args, $apidata);
-    }
-
-    function user_timeline($args, $apidata)
-    {
-        parent::handle($args);
-
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_("%s timeline"), $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:UserTimeline:".$user->id;
-        $link       = common_local_url('showstream',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('Updates from %1$s on %2$s!'),
-            $user->nickname, $sitename);
-
-        # FriendFeed's SUP protocol
-        # Also added RSS and Atom feeds
-
-        $suplink = common_local_url('sup', null, null, $user->id);
-        header('X-SUP-ID: '.$suplink);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        $notice = $user->getNotices(($page-1)*$count,
-            $count, $since_id, $max_id, $since);
-
-        switch($apidata['content-type']) {
-         case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-         case 'rss':
-            $this->show_rss_timeline($notice, $title, $link,
-                $subtitle, $suplink);
-            break;
-         case 'atom':
-            if (isset($apidata['api_arg'])) {
-                $selfuri = common_root_url() .
-                    'api/statuses/user_timeline/' .
-                        $apidata['api_arg'] . '.atom';
-            } else {
-                $selfuri = common_root_url() .
-                 'api/statuses/user_timeline.atom';
-            }
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, $suplink, $selfuri);
-            break;
-         case 'json':
-            $this->show_json_timeline($notice);
-            break;
-         default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function update($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];  // Always the auth user
-
-        $status = $this->trimmed('status');
-        $source = $this->trimmed('source');
-        $in_reply_to_status_id =
-            intval($this->trimmed('in_reply_to_status_id'));
-        $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
-
-        if (empty($source) || in_array($source, $reserved_sources)) {
-            $source = 'api';
-        }
-
-        if (empty($status)) {
-
-            // XXX: Note: In this case, Twitter simply returns '200 OK'
-            // No error is given, but the status is not posted to the
-            // user's timeline.     Seems bad.     Shouldn't we throw an
-            // errror? -- Zach
-            return;
-
-        } else {
-
-            $status_shortened = common_shorten_links($status);
-
-            if (Notice::contentTooLong($status_shortened)) {
-
-                // XXX: Twitter truncates anything over 140, flags the status
-                // as "truncated." Sending this error may screw up some clients
-                // that assume Twitter will truncate for them.    Should we just
-                // truncate too? -- Zach
-                $this->clientError(sprintf(_('That\'s too long. Max notice size is %d chars.'),
-                                           Notice::maxContent()),
-                                   $code = 406, $apidata['content-type']);
-                return;
-            }
-        }
-
-        // Check for commands
-        $inter = new CommandInterpreter();
-        $cmd = $inter->handle_command($user, $status_shortened);
-
-        if ($cmd) {
-
-            if ($this->supported($cmd)) {
-                $cmd->execute(new Channel());
-            }
-
-            // cmd not supported?  Twitter just returns your latest status.
-            // And, it returns your last status whether the cmd was successful
-            // or not!
-            $n = $user->getCurrentNotice();
-            $apidata['api_arg'] = $n->id;
-        } else {
-
-            $reply_to = null;
-
-            if ($in_reply_to_status_id) {
-
-                // check whether notice actually exists
-                $reply = Notice::staticGet($in_reply_to_status_id);
-
-                if ($reply) {
-                    $reply_to = $in_reply_to_status_id;
-                } else {
-                    $this->clientError(_('Not found'), $code = 404,
-                        $apidata['content-type']);
-                    return;
-                }
-            }
-
-            $notice = Notice::saveNew($user->id,
-                html_entity_decode($status, ENT_NOQUOTES, 'UTF-8'),
-                    $source, 1, $reply_to);
-
-            common_broadcast_notice($notice);
-            $apidata['api_arg'] = $notice->id;
-        }
-
-        $this->show($args, $apidata);
-    }
-
-    function mentions($args, $apidata)
-    {
-        parent::handle($args);
-
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-        $this->auth_user = $apidata['user'];
-
-        if (empty($user)) {
-             $this->clientError(_('No such user!'), 404,
-                 $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_('%1$s / Updates mentioning %2$s'),
-            $sitename, $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:Mentions:".$user->id;
-        $link       = common_local_url('replies',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('%1$s updates that reply to updates from %2$s / %3$s.'),
-            $sitename, $user->nickname, $profile->getBestName());
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        $notice = $user->getReplies(($page-1)*$count,
-            $count, $since_id, $max_id, $since);
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            $selfuri = common_root_url() .
-                ltrim($_SERVER['QUERY_STRING'], 'p=');
-            $this->show_atom_timeline($notice, $title, $id, $link, $subtitle,
-                null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function replies($args, $apidata)
-    {
-        call_user_func(array($this, 'mentions'), $args, $apidata);
-    }
-
-    function show($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        // 'id' is an undocumented parameter in Twitter's API. Several
-        // clients make use of it, so we support it too.
-
-        // show.json?id=12345 takes precedence over /show/12345.json
-
-        $this->auth_user = $apidata['user'];
-        $notice_id       = $this->trimmed('id');
-
-        if (empty($notice_id)) {
-            $notice_id   = $apidata['api_arg'];
-        }
-
-        $notice          = Notice::staticGet((int)$notice_id);
-
-        if ($notice) {
-            if ($apidata['content-type'] == 'xml') {
-                $this->show_single_xml_status($notice);
-            } elseif ($apidata['content-type'] == 'json') {
-                $this->show_single_json_status($notice);
-            }
-        } else {
-            // XXX: Twitter just sets a 404 header and doens't bother
-            // to return an err msg
-            $deleted = Deleted_notice::staticGet($notice_id);
-            if (!empty($deleted)) {
-                $this->clientError(_('Status deleted.'),
-                                   410, $apidata['content-type']);
-            } else {
-                $this->clientError(_('No status with that ID found.'),
-                                   404, $apidata['content-type']);
-            }
-        }
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        // Check for RESTfulness
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            // XXX: Twitter just prints the err msg, no XML / JSON.
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $user      = $apidata['user']; // Always the auth user
-        $notice_id = $apidata['api_arg'];
-        $notice    = Notice::staticGet($notice_id);
-
-        if (empty($notice)) {
-            $this->clientError(_('No status found with that ID.'),
-                404, $apidata['content-type']);
-            return;
-        }
-
-        if ($user->id == $notice->profile_id) {
-            $replies = new Reply;
-            $replies->get('notice_id', $notice_id);
-            $replies->delete();
-            $notice->delete();
-
-            if ($apidata['content-type'] == 'xml') {
-                $this->show_single_xml_status($notice);
-            } elseif ($apidata['content-type'] == 'json') {
-                $this->show_single_json_status($notice);
-            }
-        } else {
-            $this->clientError(_('You may not delete another user\'s status.'),
-                403, $apidata['content-type']);
-        }
-
-    }
-
-    function friends($args, $apidata)
-    {
-        parent::handle($args);
-        $includeStatuses= !(array_key_exists('lite', $args) and $args['lite']);
-        return $this->subscriptions($apidata, 'subscribed', 'subscriber', false, $includeStatuses);
-    }
-
-    function friendsIDs($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->subscriptions($apidata, 'subscribed', 'subscriber', true);
-    }
-
-    function followers($args, $apidata)
-    {
-        parent::handle($args);
-        $includeStatuses= !(array_key_exists('lite', $args) and $args['lite']);
-        return $this->subscriptions($apidata, 'subscriber', 'subscribed', false, $includeStatuses);
-    }
-
-    function followersIDs($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->subscriptions($apidata, 'subscriber', 'subscribed', true);
-    }
-
-    function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false, $includeStatuses=true)
-    {
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sub = new Subscription();
-        $sub->$user_attr = $profile->id;
-
-        $sub->orderBy('created DESC');
-
-        // Normally, page 100 friends at a time
-
-        if (!$onlyIDs) {
-            $page  = $this->arg('page', 1);
-            $count = $this->arg('count', 100);
-            $sub->limit(($page-1)*$count, $count);
-        } else {
-
-            // If we're just looking at IDs, return
-            // ALL of them, unless the user specifies a page,
-            // in which case, return 500 per page.
-
-            $page = $this->arg('page');
-            if (!empty($page)) {
-                if ($page < 1) {
-                    $page = 1;
-                }
-                $count = 500;
-                $sub->limit(($page-1)*$count, $count);
-            }
-        }
-
-        $others = array();
-
-        if ($sub->find()) {
-            while ($sub->fetch()) {
-                $others[] = Profile::staticGet($sub->$other_attr);
-            }
-        } else {
-            // user has no followers
-        }
-
-        $type = $apidata['content-type'];
-
-        $this->init_document($type);
-
-        if ($onlyIDs) {
-            $this->showIDs($others, $type);
-        } else {
-            $this->show_profiles($others, $type, $includeStatuses);
-        }
-
-        $this->end_document($type);
-    }
-
-    function show_profiles($profiles, $type, $includeStatuses)
-    {
-        switch ($type) {
-        case 'xml':
-            $this->elementStart('users', array('type' => 'array'));
-            foreach ($profiles as $profile) {
-                $this->show_profile($profile,$type,null,$includeStatuses);
-            }
-            $this->elementEnd('users');
-            break;
-        case 'json':
-            $arrays = array();
-            foreach ($profiles as $profile) {
-                $arrays[] = $this->twitter_user_array($profile, $includeStatuses);
-            }
-            print json_encode($arrays);
-            break;
-        default:
-            $this->clientError(_('unsupported file type'));
-        }
-    }
-
-    function showIDs($profiles, $type)
-    {
-        switch ($type) {
-        case 'xml':
-            $this->elementStart('ids');
-            foreach ($profiles as $profile) {
-                $this->element('id', null, $profile->id);
-            }
-            $this->elementEnd('ids');
-            break;
-        case 'json':
-            $ids = array();
-            foreach ($profiles as $profile) {
-                $ids[] = (int)$profile->id;
-            }
-            print json_encode($ids);
-            break;
-        default:
-            $this->clientError(_('unsupported file type'));
-        }
-    }
-
-    function featured($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function supported($cmd)
-    {
-        $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand',
-            'FavCommand', 'OnCommand', 'OffCommand');
-
-        if (in_array(get_class($cmd), $cmdlist)) {
-            return true;
-        }
-
-        return false;
-    }
-
-}
diff --git a/actions/twitapistatusnet.php b/actions/twitapistatusnet.php
deleted file mode 100644 (file)
index 490f11d..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * StatusNet-only extensions to the Twitter-like 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  Twitter
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @copyright 2008 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') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/twitterapi.php';
-
-/**
- * StatusNet-specific API methods
- *
- * This class handles all /statusnet/ API methods.
- *
- * @category  Twitter
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @copyright 2008 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/
- */
-
-class TwitapistatusnetAction extends TwitterapiAction
-{
-    /**
-     * A version stamp for the API
-     *
-     * Returns a version number for this version of StatusNet, which
-     * should make things a bit easier for upgrades.
-     * URL: http://identi.ca/api/statusnet/version.(xml|json)
-     * Formats: xml, json
-     *
-     * @param array $args    Web arguments
-     * @param array $apidata Twitter API data
-     *
-     * @return void
-     *
-     * @see ApiAction::process_command()
-     */
-
-    function version($args, $apidata)
-    {
-        parent::handle($args);
-        switch ($apidata['content-type']) {
-         case 'xml':
-            $this->init_document('xml');
-            $this->element('version', null, STATUSNET_VERSION);
-            $this->end_document('xml');
-            break;
-         case 'json':
-            $this->init_document('json');
-            print '"'.STATUSNET_VERSION.'"';
-            $this->end_document('json');
-            break;
-         default:
-            $this->clientError(_('API method not found!'), $code=404);
-        }
-    }
-
-    /**
-     * Dump of configuration variables
-     *
-     * Gives a full dump of configuration variables for this instance
-     * of StatusNet, minus variables that may be security-sensitive (like
-     * passwords).
-     * URL: http://identi.ca/api/statusnet/config.(xml|json)
-     * Formats: xml, json
-     *
-     * @param array $args    Web arguments
-     * @param array $apidata Twitter API data
-     *
-     * @return void
-     *
-     * @see ApiAction::process_command()
-     */
-
-    function config($args, $apidata)
-    {
-        static $keys = array('site' => array('name', 'server', 'theme', 'path', 'fancy', 'language',
-                                             'email', 'broughtby', 'broughtbyurl', 'closed',
-                                             'inviteonly', 'private'),
-                             'license' => array('url', 'title', 'image'),
-                             'nickname' => array('featured'),
-                             'throttle' => array('enabled', 'count', 'timespan'),
-                             'xmpp' => array('enabled', 'server', 'user'));
-
-        parent::handle($args);
-
-        switch ($apidata['content-type']) {
-         case 'xml':
-            $this->init_document('xml');
-            $this->elementStart('config');
-            // XXX: check that all sections and settings are legal XML elements
-            foreach ($keys as $section => $settings) {
-                $this->elementStart($section);
-                foreach ($settings as $setting) {
-                    $value = common_config($section, $setting);
-                    if (is_array($value)) {
-                        $value = implode(',', $value);
-                    } else if ($value === false) {
-                        $value = 'false';
-                    } else if ($value === true) {
-                        $value = 'true';
-                    }
-                    $this->element($setting, null, $value);
-                }
-                $this->elementEnd($section);
-            }
-            $this->elementEnd('config');
-            $this->end_document('xml');
-            break;
-         case 'json':
-            $result = array();
-            foreach ($keys as $section => $settings) {
-                $result[$section] = array();
-                foreach ($settings as $setting) {
-                    $result[$section][$setting] = common_config($section, $setting);
-                }
-            }
-            $this->init_document('json');
-            $this->show_json_objects($result);
-            $this->end_document('json');
-            break;
-         default:
-            $this->clientError(_('API method not found!'), $code=404);
-        }
-    }
-
-    /**
-     * WADL description of the API
-     *
-     * Gives a WADL description of the API provided by this version of the
-     * software.
-     *
-     * @param array $args    Web arguments
-     * @param array $apidata Twitter API data
-     *
-     * @return void
-     *
-     * @see ApiAction::process_command()
-     */
-
-    function wadl($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), 501);
-    }
-
-}
diff --git a/actions/twitapitags.php b/actions/twitapitags.php
deleted file mode 100644 (file)
index 0bcc55d..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * StatusNet extensions to the Twitter-like API for groups
- *
- * 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  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 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') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/twitterapi.php';
-
-/**
- * Group-specific API methods
- *
- * This class handles StatusNet group API methods.
- *
- * @category  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 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/
- */
-
- class TwitapitagsAction extends TwitterapiAction
- {
-
-     function timeline($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in tags api action");
-
-         $this->auth_user = $apidata['user'];
-         $tag = $apidata['api_arg'];
-
-         if (empty($tag)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("Notices tagged with %s"), $tag);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:TagTimeline:".$tag;
-         $link       = common_local_url('tag',
-             array('tag' => $tag));
-         $subtitle   = sprintf(_('Updates tagged with %1$s on %2$s!'),
-             $tag, $sitename);
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         # XXX: support max_id, since_id, and since arguments
-         $notice = Notice_tag::getStream($tag, ($page-1)*$count, $count + 1);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_xml_timeline($notice);
-             break;
-          case 'rss':
-             $this->show_rss_timeline($notice, $title, $link, $subtitle);
-             break;
-          case 'atom':
-             if (isset($apidata['api_arg'])) {
-                 $selfuri = common_root_url() .
-                     'api/statusnet/tags/timeline/' .
-                         $apidata['api_arg'] . '.atom';
-             } else {
-                 $selfuri = common_root_url() .
-                  'api/statusnet/tags/timeline.atom';
-             }
-             $this->show_atom_timeline($notice, $title, $id, $link,
-                 $subtitle, null, $selfuri);
-             break;
-          case 'json':
-             $this->show_json_timeline($notice);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-}
index 83ab28f35d6236fa233c6f1f0a7b6aee5e82e039..779405e6d64c7a41d2e88f77e9a0915691ca5350 100644 (file)
@@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/twitterapi.php';
+require_once INSTALLDIR.'/lib/api.php';
 
 /**
  *  Returns the top ten queries that are currently trending
@@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://status.net/
  *
- * @see      TwitterapiAction
+ * @see      ApiAction
  */
 
-class TwitapitrendsAction extends TwitterapiAction
+class TwitapitrendsAction extends ApiAction
 {
 
     var $callback;
diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php
deleted file mode 100644 (file)
index 703fa67..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/twitterapi.php');
-
-class TwitapiusersAction extends TwitterapiAction
-{
-
-    function show($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user = null;
-        $email = $this->arg('email');
-
-        // XXX: email field deprecated in Twitter's API
-
-        if ($email) {
-            $user = User::staticGet('email', $email);
-        } else {
-            $user = $this->get_user($apidata['api_arg'], $apidata);
-        }
-
-        if (empty($user)) {
-            $this->clientError(_('Not found.'), 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        if (!$profile) {
-            common_server_error(_('User has no profile.'));
-            return;
-        }
-
-        $twitter_user = $this->twitter_user_array($user->getProfile(), true);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->init_document('xml');
-            $this->show_twitter_xml_user($twitter_user);
-            $this->end_document('xml');
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->init_document('json');
-            $this->show_json_objects($twitter_user);
-            $this->end_document('json');
-        } else {
-
-            // This is in case 'show' was called via /account/verify_credentials
-            // without a format (xml or json).
-            header('Content-Type: text/html; charset=utf-8');
-            print 'Authorized';
-        }
-
-    }
-}
diff --git a/lib/api.php b/lib/api.php
new file mode 100644 (file)
index 0000000..7a63a4a
--- /dev/null
@@ -0,0 +1,1216 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base API action
+ *
+ * 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    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Toby Inkster <mail@tobyinkster.co.uk>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+/**
+ * Contains most of the Twitter-compatible API output functions.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Toby Inkster <mail@tobyinkster.co.uk>
+ * @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 ApiAction extends Action
+{
+     var $format   = null;
+     var $user     = null;
+     var $page     = null;
+     var $count    = null;
+     var $max_id   = null;
+     var $since_id = null;
+     var $since    = null;
+     
+    /**
+     * Initialization.
+     *
+     * @param array $args Web and URL arguments
+     *
+     * @return boolean false if user doesn't exist
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        
+        $this->format   = $this->arg('format');
+        $this->page     = (int)$this->arg('page', 1);
+        $this->count    = (int)$this->arg('count', 20);
+        $this->max_id   = (int)$this->arg('max_id', 0);
+        $this->since_id = (int)$this->arg('since_id', 0);
+        $this->since    = $this->arg('since');
+        
+        return true;
+    }
+
+    /**
+     * Handle a request
+     *
+     * @param array $args Arguments from $_REQUEST
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+    }
+
+    /**
+     * Overrides XMLOutputter::element to write booleans as strings (true|false).
+     * See that method's documentation for more info.
+     *
+     * @param string $tag     Element type or tagname
+     * @param array  $attrs   Array of element attributes, as
+     *                        key-value pairs
+     * @param string $content string content of the element
+     *
+     * @return void
+     */
+    function element($tag, $attrs=null, $content=null)
+    {
+        if (is_bool($content)) {
+            $content = ($content ? 'true' : 'false');
+        }
+
+        return parent::element($tag, $attrs, $content);
+    }
+
+    function twitterUserArray($profile, $get_notice=false)
+    {
+        $twitter_user = array();
+
+        $twitter_user['id'] = intval($profile->id);
+        $twitter_user['name'] = $profile->getBestName();
+        $twitter_user['screen_name'] = $profile->nickname;
+        $twitter_user['location'] = ($profile->location) ? $profile->location : null;
+        $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
+
+        $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+        $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
+            Avatar::defaultImage(AVATAR_STREAM_SIZE);
+
+        $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
+        $twitter_user['protected'] = false; # not supported by StatusNet yet
+        $twitter_user['followers_count'] = $profile->subscriberCount();
+
+        // To be supported soon...
+        $twitter_user['profile_background_color'] = '';
+        $twitter_user['profile_text_color'] = '';
+        $twitter_user['profile_link_color'] = '';
+        $twitter_user['profile_sidebar_fill_color'] = '';
+        $twitter_user['profile_sidebar_border_color'] = '';
+
+        $twitter_user['friends_count'] = $profile->subscriptionCount();
+
+        $twitter_user['created_at'] = $this->dateTwitter($profile->created);
+
+        $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
+
+        // Need to pull up the user for some of this
+        $user = User::staticGet($profile->id);
+
+        $timezone = 'UTC';
+
+        if ($user->timezone) {
+            $timezone = $user->timezone;
+        }
+
+        $t = new DateTime;
+        $t->setTimezone(new DateTimeZone($timezone));
+
+        $twitter_user['utc_offset'] = $t->format('Z');
+        $twitter_user['time_zone'] = $timezone;
+
+        // To be supported some day, perhaps
+        $twitter_user['profile_background_image_url'] = '';
+        $twitter_user['profile_background_tile'] = false;
+
+        $twitter_user['statuses_count'] = $profile->noticeCount();
+
+        // Is the requesting user following this user?
+        $twitter_user['following'] = false;
+        $twitter_user['notifications'] = false;
+
+        if (isset($apidata['user'])) {
+
+            $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
+
+            // Notifications on?
+            $sub = Subscription::pkeyGet(array('subscriber' =>
+                $apidata['user']->id, 'subscribed' => $profile->id));
+
+            if ($sub) {
+                $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
+            }
+        }
+
+        if ($get_notice) {
+            $notice = $profile->getCurrentNotice();
+            if ($notice) {
+                # don't get user!
+                $twitter_user['status'] = $this->twitterStatusArray($notice, false);
+            }
+        }
+
+        return $twitter_user;
+    }
+
+    function twitterStatusArray($notice, $include_user=true)
+    {
+        $profile = $notice->getProfile();
+
+        $twitter_status = array();
+        $twitter_status['text'] = $notice->content;
+        $twitter_status['truncated'] = false; # Not possible on StatusNet
+        $twitter_status['created_at'] = $this->dateTwitter($notice->created);
+        $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
+            intval($notice->reply_to) : null;
+        $twitter_status['source'] = $this->sourceLink($notice->source);
+        $twitter_status['id'] = intval($notice->id);
+
+        $replier_profile = null;
+
+        if ($notice->reply_to) {
+            $reply = Notice::staticGet(intval($notice->reply_to));
+            if ($reply) {
+                $replier_profile = $reply->getProfile();
+            }
+        }
+
+        $twitter_status['in_reply_to_user_id'] =
+            ($replier_profile) ? intval($replier_profile->id) : null;
+        $twitter_status['in_reply_to_screen_name'] =
+            ($replier_profile) ? $replier_profile->nickname : null;
+
+        if (isset($this->auth_user)) {
+            $twitter_status['favorited'] = $this->auth_user->hasFave($notice);
+        } else {
+            $twitter_status['favorited'] = false;
+        }
+
+        // Enclosures
+        $attachments = $notice->attachments();
+
+        if (!empty($attachments)) {
+
+            $twitter_status['attachments'] = array();
+
+            foreach ($attachments as $attachment) {
+                if ($attachment->isEnclosure()) {
+                    $enclosure = array();
+                    $enclosure['url'] = $attachment->url;
+                    $enclosure['mimetype'] = $attachment->mimetype;
+                    $enclosure['size'] = $attachment->size;
+                    $twitter_status['attachments'][] = $enclosure;
+                }
+            }
+        }
+
+        if ($include_user) {
+            # Don't get notice (recursive!)
+            $twitter_user = $this->twitterUserArray($profile, false);
+            $twitter_status['user'] = $twitter_user;
+        }
+
+        return $twitter_status;
+    }
+
+    function twitterGroupArray($group)
+    {
+        $twitter_group=array();
+        $twitter_group['id']=$group->id;
+        $twitter_group['url']=$group->permalink();
+        $twitter_group['nickname']=$group->nickname;
+        $twitter_group['fullname']=$group->fullname;
+        $twitter_group['homepage_url']=$group->homepage_url;
+        $twitter_group['original_logo']=$group->original_logo;
+        $twitter_group['homepage_logo']=$group->homepage_logo;
+        $twitter_group['stream_logo']=$group->stream_logo;
+        $twitter_group['mini_logo']=$group->mini_logo;
+        $twitter_group['homepage']=$group->homepage;
+        $twitter_group['description']=$group->description;
+        $twitter_group['location']=$group->location;
+        $twitter_group['created']=$this->dateTwitter($group->created);
+        $twitter_group['modified']=$this->dateTwitter($group->modified);
+        return $twitter_group;
+    }
+
+    function twitterRssGroupArray($group)
+    {
+        $entry = array();
+        $entry['content']=$group->description;
+        $entry['title']=$group->nickname;
+        $entry['link']=$group->permalink();
+        $entry['published']=common_date_iso8601($group->created);
+        $entry['updated']==common_date_iso8601($group->modified);
+        $taguribase = common_config('integration', 'groupuri');
+        $entry['id'] = "group:$groupuribase:$entry[link]";
+
+        $entry['description'] = $entry['content'];
+        $entry['pubDate'] = common_date_rfc2822($group->created);
+        $entry['guid'] = $entry['link'];
+
+        return $entry;
+    }
+
+    function twitterRssEntryArray($notice)
+    {
+        $profile = $notice->getProfile();
+        $entry = array();
+
+        // We trim() to avoid extraneous whitespace in the output
+
+        $entry['content'] = common_xml_safe_str(trim($notice->rendered));
+        $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
+        $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
+        $entry['published'] = common_date_iso8601($notice->created);
+
+        $taguribase = common_config('integration', 'taguri');
+        $entry['id'] = "tag:$taguribase:$entry[link]";
+
+        $entry['updated'] = $entry['published'];
+        $entry['author'] = $profile->getBestName();
+
+        // Enclosures
+        $attachments = $notice->attachments();
+        $enclosures = array();
+
+        foreach ($attachments as $attachment) {
+            $enclosure_o=$attachment->getEnclosure();
+            if ($enclosure_o) {
+                 $enclosure = array();
+                 $enclosure['url'] = $enclosure_o->url;
+                 $enclosure['mimetype'] = $enclosure_o->mimetype;
+                 $enclosure['size'] = $enclosure_o->size;
+                 $enclosures[] = $enclosure;
+            }
+        }
+
+        if (!empty($enclosures)) {
+            $entry['enclosures'] = $enclosures;
+        }
+
+        // Tags/Categories
+        $tag = new Notice_tag();
+        $tag->notice_id = $notice->id;
+        if ($tag->find()) {
+            $entry['tags']=array();
+            while ($tag->fetch()) {
+                $entry['tags'][]=$tag->tag;
+            }
+        }
+        $tag->free();
+
+        // RSS Item specific
+        $entry['description'] = $entry['content'];
+        $entry['pubDate'] = common_date_rfc2822($notice->created);
+        $entry['guid'] = $entry['link'];
+
+        return $entry;
+    }
+
+
+    function twitterRelationshipArray($source, $target)
+    {
+        $relationship = array();
+
+        $relationship['source'] =
+            $this->relationshipDetailsArray($source, $target);
+        $relationship['target'] =
+            $this->relationshipDetailsArray($target, $source);
+
+        return array('relationship' => $relationship);
+    }
+
+    function relationshipDetailsArray($source, $target)
+    {
+        $details = array();
+
+        $details['screen_name'] = $source->nickname;
+        $details['followed_by'] = $target->isSubscribed($source);
+        $details['following'] = $source->isSubscribed($target);
+
+        $notifications = false;
+
+        if ($source->isSubscribed($target)) {
+
+            $sub = Subscription::pkeyGet(array('subscriber' =>
+                $source->id, 'subscribed' => $target->id));
+
+            if (!empty($sub)) {
+                $notifications = ($sub->jabber || $sub->sms);
+            }
+        }
+
+        $details['notifications_enabled'] = $notifications;
+        $details['blocking'] = $source->hasBlocked($target);
+        $details['id'] = $source->id;
+
+        return $details;
+    }
+
+    function showTwitterXmlRelationship($relationship)
+    {
+        $this->elementStart('relationship');
+
+        foreach($relationship as $element => $value) {
+            if ($element == 'source' || $element == 'target') {
+                $this->elementStart($element);
+                $this->showXmlRelationshipDetails($value);
+                $this->elementEnd($element);
+            }
+        }
+
+        $this->elementEnd('relationship');
+    }
+
+    function showXmlRelationshipDetails($details)
+    {
+        foreach($details as $element => $value) {
+            $this->element($element, null, $value);
+        }
+    }
+
+    function showTwitterXmlStatus($twitter_status)
+    {
+        $this->elementStart('status');
+        foreach($twitter_status as $element => $value) {
+            switch ($element) {
+            case 'user':
+                $this->showTwitterXmlUser($twitter_status['user']);
+                break;
+            case 'text':
+                $this->element($element, null, common_xml_safe_str($value));
+                break;
+            case 'attachments':
+                $this->showXmlAttachments($twitter_status['attachments']);
+                break;
+            default:
+                $this->element($element, null, $value);
+            }
+        }
+        $this->elementEnd('status');
+    }
+
+    function showTwitterXmlGroup($twitter_group)
+    {
+        $this->elementStart('group');
+        foreach($twitter_group as $element => $value) {
+            $this->element($element, null, $value);
+        }
+        $this->elementEnd('group');
+    }
+
+    function showTwitterXmlUser($twitter_user, $role='user')
+    {
+        $this->elementStart($role);
+        foreach($twitter_user as $element => $value) {
+            if ($element == 'status') {
+                $this->showTwitterXmlStatus($twitter_user['status']);
+            } else {
+                $this->element($element, null, $value);
+            }
+        }
+        $this->elementEnd($role);
+    }
+
+    function showXmlAttachments($attachments) {
+        if (!empty($attachments)) {
+            $this->elementStart('attachments', array('type' => 'array'));
+            foreach ($attachments as $attachment) {
+                $attrs = array();
+                $attrs['url'] = $attachment['url'];
+                $attrs['mimetype'] = $attachment['mimetype'];
+                $attrs['size'] = $attachment['size'];
+                $this->element('enclosure', $attrs, '');
+            }
+            $this->elementEnd('attachments');
+        }
+    }
+
+    function showTwitterRssItem($entry)
+    {
+        $this->elementStart('item');
+        $this->element('title', null, $entry['title']);
+        $this->element('description', null, $entry['description']);
+        $this->element('pubDate', null, $entry['pubDate']);
+        $this->element('guid', null, $entry['guid']);
+        $this->element('link', null, $entry['link']);
+
+        # RSS only supports 1 enclosure per item
+        if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
+            $enclosure = $entry['enclosures'][0];
+            $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
+        }
+
+        if(array_key_exists('tags', $entry)){
+            foreach($entry['tags'] as $tag){
+                $this->element('category', null,$tag);
+            }
+        }
+
+        $this->elementEnd('item');
+    }
+
+    function showJsonObjects($objects)
+    {
+        print(json_encode($objects));
+    }
+
+    function showSingleXmlStatus($notice)
+    {
+        $this->initDocument('xml');
+        $twitter_status = $this->twitterStatusArray($notice);
+        $this->showTwitterXmlStatus($twitter_status);
+        $this->endDocument('xml');
+    }
+
+    function show_single_json_status($notice)
+    {
+        $this->initDocument('json');
+        $status = $this->twitterStatusArray($notice);
+        $this->showJsonObjects($status);
+        $this->endDocument('json');
+    }
+
+
+    function showXmlTimeline($notice)
+    {
+
+        $this->initDocument('xml');
+        $this->elementStart('statuses', array('type' => 'array'));
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $twitter_status = $this->twitterStatusArray($n);
+                $this->showTwitterXmlStatus($twitter_status);
+            }
+        } else {
+            while ($notice->fetch()) {
+                $twitter_status = $this->twitterStatusArray($notice);
+                $this->showTwitterXmlStatus($twitter_status);
+            }
+        }
+
+        $this->elementEnd('statuses');
+        $this->endDocument('xml');
+    }
+
+    function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null)
+    {
+
+        $this->initDocument('rss');
+
+        $this->element('title', null, $title);
+        $this->element('link', null, $link);
+        if (!is_null($suplink)) {
+            // For FriendFeed's SUP protocol
+            $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
+                                         'rel' => 'http://api.friendfeed.com/2008/03#sup',
+                                         'href' => $suplink,
+                                         'type' => 'application/json'));
+        }
+        $this->element('description', null, $subtitle);
+        $this->element('language', null, 'en-us');
+        $this->element('ttl', null, '40');
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $entry = $this->twitterRssEntryArray($n);
+                $this->showTwitterRssItem($entry);
+            }
+        } else {
+            while ($notice->fetch()) {
+                $entry = $this->twitterRssEntryArray($notice);
+                $this->showTwitterRssItem($entry);
+            }
+        }
+
+        $this->endTwitterRss();
+    }
+
+    function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
+    {
+
+        $this->initDocument('atom');
+
+        $this->element('title', null, $title);
+        $this->element('id', null, $id);
+        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
+
+        if (!is_null($suplink)) {
+            # For FriendFeed's SUP protocol
+            $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
+                                         'href' => $suplink,
+                                         'type' => 'application/json'));
+        }
+
+        if (!is_null($selfuri)) {
+            $this->element('link', array('href' => $selfuri,
+                'rel' => 'self', 'type' => 'application/atom+xml'), null);
+        }
+
+        $this->element('updated', null, common_date_iso8601('now'));
+        $this->element('subtitle', null, $subtitle);
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $this->raw($n->asAtomEntry());
+            }
+        } else {
+            while ($notice->fetch()) {
+                $this->raw($notice->asAtomEntry());
+            }
+        }
+
+        $this->endDocument('atom');
+
+    }
+
+    function showRssGroups($group, $title, $link, $subtitle)
+    {
+
+        $this->initDocument('rss');
+
+        $this->element('title', null, $title);
+        $this->element('link', null, $link);
+        $this->element('description', null, $subtitle);
+        $this->element('language', null, 'en-us');
+        $this->element('ttl', null, '40');
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $twitter_group = $this->twitterRssGroupArray($g);
+                $this->showTwitterRssItem($twitter_group);
+            }
+        } else {
+            while ($group->fetch()) {
+                $twitter_group = $this->twitterRssGroupArray($group);
+                $this->showTwitterRssItem($twitter_group);
+            }
+        }
+
+        $this->endTwitterRss();
+    }
+
+
+    function showTwitterAtomEntry($entry)
+    {
+        $this->elementStart('entry');
+        $this->element('title', null, $entry['title']);
+        $this->element('content', array('type' => 'html'), $entry['content']);
+        $this->element('id', null, $entry['id']);
+        $this->element('published', null, $entry['published']);
+        $this->element('updated', null, $entry['updated']);
+        $this->element('link', array('type' => 'text/html',
+                                     'href' => $entry['link'],
+                                     'rel' => 'alternate'));
+        $this->element('link', array('type' => $entry['avatar-type'],
+                                     'href' => $entry['avatar'],
+                                     'rel' => 'image'));
+        $this->elementStart('author');
+
+        $this->element('name', null, $entry['author-name']);
+        $this->element('uri', null, $entry['author-uri']);
+
+        $this->elementEnd('author');
+        $this->elementEnd('entry');
+    }
+
+    function showXmlDirectMessage($dm)
+    {
+        $this->elementStart('direct_message');
+        foreach($dm as $element => $value) {
+            switch ($element) {
+            case 'sender':
+            case 'recipient':
+                $this->showTwitterXmlUser($value, $element);
+                break;
+            case 'text':
+                $this->element($element, null, common_xml_safe_str($value));
+                break;
+            default:
+                $this->element($element, null, $value);
+                break;
+            }
+        }
+        $this->elementEnd('direct_message');
+    }
+
+    function directMessageArray($message)
+    {
+        $dmsg = array();
+
+        $from_profile = $message->getFrom();
+        $to_profile = $message->getTo();
+
+        $dmsg['id'] = $message->id;
+        $dmsg['sender_id'] = $message->from_profile;
+        $dmsg['text'] = trim($message->content);
+        $dmsg['recipient_id'] = $message->to_profile;
+        $dmsg['created_at'] = $this->dateTwitter($message->created);
+        $dmsg['sender_screen_name'] = $from_profile->nickname;
+        $dmsg['recipient_screen_name'] = $to_profile->nickname;
+        $dmsg['sender'] = $this->twitterUserArray($from_profile, false);
+        $dmsg['recipient'] = $this->twitterUserArray($to_profile, false);
+
+        return $dmsg;
+    }
+
+    function rssDirectMessageArray($message)
+    {
+        $entry = array();
+
+        $from = $message->getFrom();
+
+        $entry['title'] = sprintf('Message from %s to %s',
+            $from->nickname, $message->getTo()->nickname);
+
+        $entry['content'] = common_xml_safe_str($message->rendered);
+        $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
+        $entry['published'] = common_date_iso8601($message->created);
+
+        $taguribase = common_config('integration', 'taguri');
+
+        $entry['id'] = "tag:$taguribase:$entry[link]";
+        $entry['updated'] = $entry['published'];
+
+        $entry['author-name'] = $from->getBestName();
+        $entry['author-uri'] = $from->homepage;
+
+        $avatar = $from->getAvatar(AVATAR_STREAM_SIZE);
+
+        $entry['avatar']      = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE);
+        $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png';
+
+        // RSS item specific
+
+        $entry['description'] = $entry['content'];
+        $entry['pubDate'] = common_date_rfc2822($message->created);
+        $entry['guid'] = $entry['link'];
+
+        return $entry;
+    }
+
+    function showSingleXmlDirectMessage($message)
+    {
+        $this->initDocument('xml');
+        $dmsg = $this->directMessageArray($message);
+        $this->showXmlDirectMessage($dmsg);
+        $this->endDocument('xml');
+    }
+
+    function showSingleJsonDirectMessage($message)
+    {
+        $this->initDocument('json');
+        $dmsg = $this->directMessageArray($message);
+        $this->showJsonObjects($dmsg);
+        $this->endDocument('json');
+    }
+
+    function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
+    {
+
+        $this->initDocument('atom');
+
+        $this->element('title', null, $title);
+        $this->element('id', null, $id);
+        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
+
+        if (!is_null($selfuri)) {
+            $this->element('link', array('href' => $selfuri,
+                'rel' => 'self', 'type' => 'application/atom+xml'), null);
+        }
+
+        $this->element('updated', null, common_date_iso8601('now'));
+        $this->element('subtitle', null, $subtitle);
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $this->raw($g->asAtomEntry());
+            }
+        } else {
+            while ($group->fetch()) {
+                $this->raw($group->asAtomEntry());
+            }
+        }
+
+        $this->endDocument('atom');
+
+    }
+
+    function showJsonTimeline($notice)
+    {
+
+        $this->initDocument('json');
+
+        $statuses = array();
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $twitter_status = $this->twitterStatusArray($n);
+                array_push($statuses, $twitter_status);
+            }
+        } else {
+            while ($notice->fetch()) {
+                $twitter_status = $this->twitterStatusArray($notice);
+                array_push($statuses, $twitter_status);
+            }
+        }
+
+        $this->showJsonObjects($statuses);
+
+        $this->endDocument('json');
+    }
+
+    function showJsonGroups($group)
+    {
+
+        $this->initDocument('json');
+
+        $groups = array();
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $twitter_group = $this->twitterGroupArray($g);
+                array_push($groups, $twitter_group);
+            }
+        } else {
+            while ($group->fetch()) {
+                $twitter_group = $this->twitterGroupArray($group);
+                array_push($groups, $twitter_group);
+            }
+        }
+
+        $this->showJsonObjects($groups);
+
+        $this->endDocument('json');
+    }
+
+    function showXmlGroups($group)
+    {
+
+        $this->initDocument('xml');
+        $this->elementStart('groups', array('type' => 'array'));
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $twitter_group = $this->twitterGroupArray($g);
+                $this->showTwitterXmlGroup($twitter_group);
+            }
+        } else {
+            while ($group->fetch()) {
+                $twitter_group = $this->twitterGroupArray($group);
+                $this->showTwitterXmlGroup($twitter_group);
+            }
+        }
+
+        $this->elementEnd('groups');
+        $this->endDocument('xml');
+    }
+
+    function showTwitterXmlUsers($user)
+    {
+
+        $this->initDocument('xml');
+        $this->elementStart('users', array('type' => 'array'));
+
+        if (is_array($user)) {
+            foreach ($user as $u) {
+                $twitter_user = $this->twitterUserArray($u);
+                $this->showTwitterXmlUser($twitter_user);
+            }
+        } else {
+            while ($user->fetch()) {
+                $twitter_user = $this->twitterUserArray($user);
+                $this->showTwitterXmlUser($twitter_user);
+            }
+        }
+
+        $this->elementEnd('users');
+        $this->endDocument('xml');
+    }
+
+    function showJsonUsers($user)
+    {
+
+        $this->initDocument('json');
+
+        $users = array();
+
+        if (is_array($user)) {
+            foreach ($user as $u) {
+                $twitter_user = $this->twitterUserArray($u);
+                array_push($users, $twitter_user);
+            }
+        } else {
+            while ($user->fetch()) {
+                $twitter_user = $this->twitterUserArray($user);
+                array_push($users, $twitter_user);
+            }
+        }
+
+        $this->showJsonObjects($users);
+
+        $this->endDocument('json');
+    }
+
+    function showSingleJsonGroup($group)
+    {
+        $this->initDocument('json');
+        $twitter_group = $this->twitterGroupArray($group);
+        $this->showJsonObjects($twitter_group);
+        $this->endDocument('json');
+    }
+
+    function showSingleXmlGroup($group)
+    {
+        $this->initDocument('xml');
+        $twitter_group = $this->twitterGroupArray($group);
+        $this->showTwitterXmlGroup($twitter_group);
+        $this->endDocument('xml');
+    }
+
+    function dateTwitter($dt)
+    {
+        $dateStr = date('d F Y H:i:s', strtotime($dt));
+        $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+        $d->setTimezone(new DateTimeZone(common_timezone()));
+        return $d->format('D M d H:i:s O Y');
+    }
+
+    function initDocument($type='xml')
+    {
+        switch ($type) {
+        case 'xml':
+            header('Content-Type: application/xml; charset=utf-8');
+            $this->startXML();
+            break;
+        case 'json':
+            header('Content-Type: application/json; charset=utf-8');
+
+            // Check for JSONP callback
+            $callback = $this->arg('callback');
+            if ($callback) {
+                print $callback . '(';
+            }
+            break;
+        case 'rss':
+            header("Content-Type: application/rss+xml; charset=utf-8");
+            $this->initTwitterRss();
+            break;
+        case 'atom':
+            header('Content-Type: application/atom+xml; charset=utf-8');
+            $this->initTwitterAtom();
+            break;
+        default:
+            $this->clientError(_('Not a supported data format.'));
+            break;
+        }
+
+        return;
+    }
+
+    function endDocument($type='xml')
+    {
+        switch ($type) {
+        case 'xml':
+            $this->endXML();
+            break;
+        case 'json':
+
+            // Check for JSONP callback
+            $callback = $this->arg('callback');
+            if ($callback) {
+                print ')';
+            }
+            break;
+        case 'rss':
+            $this->endTwitterRss();
+            break;
+        case 'atom':
+            $this->endTwitterRss();
+            break;
+        default:
+            $this->clientError(_('Not a supported data format.'));
+            break;
+        }
+        return;
+    }
+
+    function clientError($msg, $code = 400, $format = 'xml')
+    {
+        $action = $this->trimmed('action');
+
+        common_debug("User error '$code' on '$action': $msg", __FILE__);
+
+        if (!array_key_exists($code, ClientErrorAction::$status)) {
+            $code = 400;
+        }
+
+        $status_string = ClientErrorAction::$status[$code];
+
+        header('HTTP/1.1 '.$code.' '.$status_string);
+
+        if ($format == 'xml') {
+            $this->initDocument('xml');
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endDocument('xml');
+        } elseif ($format == 'json'){
+            $this->initDocument('json');
+            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+            $this->endDocument('json');
+        } else {
+
+            // If user didn't request a useful format, throw a regular client error
+            throw new ClientException($msg, $code);
+        }
+    }
+
+    function serverError($msg, $code = 500, $content_type = 'json')
+    {
+        $action = $this->trimmed('action');
+
+        common_debug("Server error '$code' on '$action': $msg", __FILE__);
+
+        if (!array_key_exists($code, ServerErrorAction::$status)) {
+            $code = 400;
+        }
+
+        $status_string = ServerErrorAction::$status[$code];
+
+        header('HTTP/1.1 '.$code.' '.$status_string);
+
+        if ($content_type == 'xml') {
+            $this->initDocument('xml');
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endDocument('xml');
+        } else {
+            $this->initDocument('json');
+            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+            $this->endDocument('json');
+        }
+    }
+
+    function initTwitterRss()
+    {
+        $this->startXML();
+        $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom'));
+        $this->elementStart('channel');
+        Event::handle('StartApiRss', array($this));
+    }
+
+    function endTwitterRss()
+    {
+        $this->elementEnd('channel');
+        $this->elementEnd('rss');
+        $this->endXML();
+    }
+
+    function initTwitterAtom()
+    {
+        $this->startXML();
+        // FIXME: don't hardcode the language here!
+        $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
+                                          'xml:lang' => 'en-US',
+                                          'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
+        Event::handle('StartApiAtom', array($this));
+    }
+
+    function endTwitterAtom()
+    {
+        $this->elementEnd('feed');
+        $this->endXML();
+    }
+
+    function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
+    {
+        $profile_array = $this->twitterUserArray($profile, $includeStatuses);
+        switch ($content_type) {
+        case 'xml':
+            $this->showTwitterXmlUser($profile_array);
+            break;
+        case 'json':
+            $this->showJsonObjects($profile_array);
+            break;
+        default:
+            $this->clientError(_('Not a supported data format.'));
+            return;
+        }
+        return;
+    }
+
+    function getTargetUser($id)
+    {
+        if (empty($id)) {
+
+            // Twitter supports these other ways of passing the user ID
+            if (is_numeric($this->arg('id'))) {
+                return User::staticGet($this->arg('id'));
+            } else if ($this->arg('id')) {
+                $nickname = common_canonical_nickname($this->arg('id'));
+                return User::staticGet('nickname', $nickname);
+            } else if ($this->arg('user_id')) {
+                // This is to ensure that a non-numeric user_id still
+                // overrides screen_name even if it doesn't get used
+                if (is_numeric($this->arg('user_id'))) {
+                    return User::staticGet('id', $this->arg('user_id'));
+                }
+            } else if ($this->arg('screen_name')) {
+                $nickname = common_canonical_nickname($this->arg('screen_name'));
+                return User::staticGet('nickname', $nickname);
+            } else {
+                // Fall back to trying the currently authenticated user
+                return $this->auth_user;
+            }
+
+        } else if (is_numeric($id)) {
+            return User::staticGet($id);
+        } else {
+            $nickname = common_canonical_nickname($id);
+            return User::staticGet('nickname', $nickname);
+        }
+    }
+
+    function getTargetGroup($id)
+    {
+        if (empty($id)) {
+            if (is_numeric($this->arg('id'))) {
+                return User_group::staticGet($this->arg('id'));
+            } else if ($this->arg('id')) {
+                $nickname = common_canonical_nickname($this->arg('id'));
+                return User_group::staticGet('nickname', $nickname);
+            } else if ($this->arg('group_id')) {
+                // This is to ensure that a non-numeric user_id still
+                // overrides screen_name even if it doesn't get used
+                if (is_numeric($this->arg('group_id'))) {
+                    return User_group::staticGet('id', $this->arg('group_id'));
+                }
+            } else if ($this->arg('group_name')) {
+                $nickname = common_canonical_nickname($this->arg('group_name'));
+                return User_group::staticGet('nickname', $nickname);
+            }
+
+        } else if (is_numeric($id)) {
+            return User_group::staticGet($id);
+        } else {
+            $nickname = common_canonical_nickname($id);
+            return User_group::staticGet('nickname', $nickname);
+        }
+    }
+
+    function sourceLink($source)
+    {
+        $source_name = _($source);
+        switch ($source) {
+        case 'web':
+        case 'xmpp':
+        case 'mail':
+        case 'omb':
+        case 'api':
+            break;
+        default:
+            $ns = Notice_source::staticGet($source);
+            if ($ns) {
+                $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
+            }
+            break;
+        }
+        return $source_name;
+    }
+
+    /**
+     * Returns query argument or default value if not found. Certain
+     * parameters used throughout the API are lightly scrubbed and
+     * bounds checked.  This overrides Action::arg().
+     *
+     * @param string $key requested argument
+     * @param string $def default value to return if $key is not provided
+     *
+     * @return var $var
+     */
+    function arg($key, $def=null)
+    {
+
+        // XXX: Do even more input validation/scrubbing?
+
+        if (array_key_exists($key, $this->args)) {
+            switch($key) {
+            case 'page':
+                $page = (int)$this->args['page'];
+                return ($page < 1) ? 1 : $page;
+            case 'count':
+                $count = (int)$this->args['count'];
+                if ($count < 1) {
+                    return 20;
+                } elseif ($count > 200) {
+                    return 200;
+                } else {
+                    return $count;
+                }
+            case 'since_id':
+                $since_id = (int)$this->args['since_id'];
+                return ($since_id < 1) ? 0 : $since_id;
+            case 'max_id':
+                $max_id = (int)$this->args['max_id'];
+                return ($max_id < 1) ? 0 : $max_id;
+            case 'since':
+                return strtotime($this->args['since']);
+            default:
+                return parent::arg($key, $def);
+            }
+        } else {
+            return $def;
+        }
+    }
+
+}
diff --git a/lib/apiauth.php b/lib/apiauth.php
new file mode 100644 (file)
index 0000000..2f2e44a
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base class for API actions that require authentication
+ *
+ * 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    Adrian Lang <mail@adrianlang.de>
+ * @author    Brenda Wallace <shiny@cpan.org>
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    mEDI <medi@milaro.net>
+ * @author    Sarven Capadisli <csarven@status.net>
+ * @author    Zach Copley <zach@status.net> 
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Actions extending this class will require auth
+ *
+ * @category API
+ * @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 ApiAuthAction extends ApiAction
+{
+
+    var $auth_user = null;
+
+    /**
+     * Take arguments for running, and output basic auth header if needed
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        if ($this->requiresAuth()) {
+            $this->checkBasicAuthUser();
+        }
+
+        return true;
+    }
+
+    /**
+     * Does this API resource require authentication?
+     *
+     * @return boolean true
+     */
+
+    function requiresAuth()
+    {
+        return true;
+    }
+
+    /**
+     * Check for a user specified via HTTP basic auth. If there isn't
+     * one, try to get one by outputting the basic auth header.
+     *
+     * @return boolean true or false
+     */
+
+    function checkBasicAuthUser()
+    {
+        $this->basicAuthProcessHeader();
+
+        $realm = common_config('site', 'name') . ' API';
+
+        if (!isset($this->auth_user)) {
+            header('WWW-Authenticate: Basic realm="' . $realm . '"');
+
+            // show error if the user clicks 'cancel'
+
+            $this->showBasicAuthError();
+            exit;
+
+        } else {
+            $nickname = $this->auth_user;
+            $password = $this->auth_pw;
+            $this->auth_user = common_check_user($nickname, $password);
+
+            if (empty($this->auth_user)) {
+
+                // basic authentication failed
+
+                list($proxy, $ip) = common_client_ip();
+                common_log(
+                    LOG_WARNING,
+                    'Failed API auth attempt, nickname = ' .
+                    "$nickname, proxy = $proxy, ip = $ip."
+                );
+                $this->showBasicAuthError();
+                exit;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Read the HTTP headers and set the auth user.  Decodes HTTP_AUTHORIZATION
+     * param to support basic auth when PHP is running in CGI mode.
+     *
+     * @return void
+     */
+
+    function basicAuthProcessHeader()
+    {
+        if (isset($_SERVER['AUTHORIZATION'])
+            || isset($_SERVER['HTTP_AUTHORIZATION'])
+        ) {
+                $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])
+                ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
+        }
+
+        if (isset($_SERVER['PHP_AUTH_USER'])) {
+            $this->auth_user = $_SERVER['PHP_AUTH_USER'];
+            $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
+        } elseif (isset($authorization_header)
+            && strstr(substr($authorization_header, 0, 5), 'Basic')) {
+
+            // decode the HTTP_AUTHORIZATION header on php-cgi server self
+            // on fcgid server the header name is AUTHORIZATION
+
+            $auth_hash = base64_decode(substr($authorization_header, 6));
+            list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
+
+            // set all to null on a empty basic auth request
+
+            if ($this->auth_user == "") {
+                $this->auth_user = null;
+                $this->auth_pw = null;
+            }
+        } else {
+            $this->auth_user = null;
+            $this->auth_pw = null;
+        }
+    }
+
+    /**
+     * Output an authentication error message.  Use XML or JSON if one
+     * of those formats is specified, otherwise output plain text
+     *
+     * @return void
+     */
+
+    function showBasicAuthError()
+    {
+        header('HTTP/1.1 401 Unauthorized');
+        $msg = 'Could not authenticate you.';
+
+        if ($this->format == 'xml') {
+            header('Content-Type: application/xml; charset=utf-8');
+            $this->startXML();
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endXML();
+        } elseif ($this->format == 'json') {
+            header('Content-Type: application/json; charset=utf-8');
+            $error_array = array('error' => $msg,
+                                 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+        } else {
+            header('Content-type: text/plain');
+            print "$msg\n";
+        }
+    }
+
+}
diff --git a/lib/apibareauth.php b/lib/apibareauth.php
new file mode 100644 (file)
index 0000000..2d29c1d
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base class for API actions that require "bare auth". Bare auth means
+ * authentication is required only if the action is called without an argument
+ * or query param specifying user id.
+ *
+ * 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    Adrian Lang <mail@adrianlang.de>
+ * @author    Brenda Wallace <shiny@cpan.org>
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    mEDI <medi@milaro.net>
+ * @author    Sarven Capadisli <csarven@status.net>
+ * @author    Zach Copley <zach@status.net> 
+ * @copyright 2009 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);
+}
+
+require_once INSTALLDIR.'/lib/apiauth.php';
+
+/**
+ * Actions extending this class will require auth unless a target
+ * user ID has been specified
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Brenda Wallace <shiny@cpan.org>
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   mEDI <medi@milaro.net>
+ * @author   Sarven Capadisli <csarven@status.net>
+ * @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 ApiBareAuthAction extends ApiAuthAction
+{
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Does this API resource require authentication?
+     *
+     * @return boolean true or false
+     */
+
+    function requiresAuth()
+    {
+        // If the site is "private", all API methods except statusnet/config
+        // need authentication
+
+        if (common_config('site', 'private')) {
+            return true;
+        }
+
+        // check whether a user has been specified somehow
+
+        $id           = $this->arg('id');
+        $user_id      = $this->arg('user_id');
+        $screen_name  = $this->arg('screen_name');
+
+        if (empty($id) && empty($user_id) && empty($screen_name)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
\ No newline at end of file
index 91f886bce1111eebe3b990118811b4822ab0eb5c..b9a45d867f0b3dba00819dc7d1ee2f6b0bb8c0ea 100644 (file)
@@ -270,22 +270,100 @@ class Router
 
         // statuses API
 
-        $m->connect('api/statuses/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(public_timeline|home_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?'));
-
-        $m->connect('api/statuses/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
+        $m->connect('api/statuses/public_timeline.:format',
+                    array('action' => 'ApiTimelinePublic',
+                    'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/friends_timeline.:format',
+                    array('action' => 'ApiTimelineFriends',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/friends_timeline/:id.:format',
+                    array('action' => 'ApiTimelineFriends',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json|rss|atom)'));
+        $m->connect('api/statuses/home_timeline.:format',
+                    array('action' => 'ApiTimelineFriends',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/home_timeline/:id.:format',
+                    array('action' => 'ApiTimelineFriends',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/user_timeline.:format',
+                    array('action' => 'ApiTimelineUser',
+                    'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/user_timeline/:id.:format',
+                    array('action' => 'ApiTimelineUser',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/mentions.:format',
+                    array('action' => 'ApiTimelineMentions',
+                    'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/mentions/:id.:format',
+                    array('action' => 'ApiTimelineMentions',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/replies.:format',
+                    array('action' => 'ApiTimelineMentions',
+                    'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/replies/:id.:format',
+                    array('action' => 'ApiTimelineMentions',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statuses/friends.:format',
+                     array('action' => 'ApiUserFriends',
+                           'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/friends/:id.:format',
+                    array('action' => 'ApiUserFriends',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/followers.:format',
+                     array('action' => 'ApiUserFollowers',
+                           'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/followers/:id.:format',
+                    array('action' => 'ApiUserFollowers',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/show.:format',
+                    array('action' => 'ApiStatusesShow',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/show/:id.:format',
+                    array('action' => 'ApiStatusesShow',
+                          'id' => '[0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/update.:format',
+                    array('action' => 'ApiStatusesUpdate',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/destroy.:format',
+                  array('action' => 'ApiStatusesDestroy',
+                        'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/destroy/:id.:format',
+                  array('action' => 'ApiStatusesDestroy',
+                        'id' => '[0-9]+',
+                        'format' => '(xml|json)'));
 
         // users
 
-        $m->connect('api/users/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'users'),
-                    array('method' => 'show(\.(xml|json))?'));
+        $m->connect('api/users/show/:id.:format',
+                    array('action' => 'ApiUserShow',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
 
         $m->connect('api/users/:method',
                     array('action' => 'api',
@@ -294,93 +372,99 @@ class Router
 
         // direct messages
 
-        foreach (array('xml', 'json') as $e) {
-            $m->connect('api/direct_messages/new.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'direct_messages',
-                              'method' => 'create.'.$e));
-        }
 
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/direct_messages.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'direct_messages',
-                              'method' => 'direct_messages.'.$e));
-        }
+        $m->connect('api/direct_messages.:format',
+                    array('action' => 'ApiDirectMessage',
+                          'format' => '(xml|json|rss|atom)'));
 
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/direct_messages/sent.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'direct_messages',
-                              'method' => 'sent.'.$e));
-        }
+        $m->connect('api/direct_messages/sent.:format',
+                    array('action' => 'ApiDirectMessage',
+                          'format' => '(xml|json|rss|atom)',
+                          'sent' => true));
 
-        $m->connect('api/direct_messages/destroy/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'direct_messages'));
+        $m->connect('api/direct_messages/new.:format',
+                     array('action' => 'ApiDirectMessageNew',
+                           'format' => '(xml|json)'));
 
         // friendships
 
-        $m->connect('api/friendships/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'friendships'),
-                    array('method' => '(create|destroy)'));
+        $m->connect('api/friendships/show.:format',
+                    array('action' => 'ApiFriendshipsShow',
+                          'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'friendships'),
-                    array('method' => '(show|exists)(\.(xml|json))'));
+        $m->connect('api/friendships/exists.:format',
+                    array('action' => 'ApiFriendshipsExists',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/create.:format',
+                    array('action' => 'ApiFriendshipsCreate',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/destroy.:format',
+                     array('action' => 'ApiFriendshipsDestroy',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/create/:id.:format',
+                    array('action' => 'ApiFriendshipsCreate',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/destroy/:id.:format',
+                    array('action' => 'ApiFriendshipsDestroy',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json)'));
 
         // Social graph
 
-        $m->connect('api/friends/ids/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses',
-                          'method' => 'friendsIDs'));
-
-        foreach (array('xml', 'json') as $e) {
-            $m->connect('api/friends/ids.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'statuses',
-                              'method' => 'friendsIDs.'.$e));
-        }
+        $m->connect('api/friends/ids/:id.:format',
+                    array('action' => 'apiFriends',
+                          'ids_only' => true));
 
-        $m->connect('api/followers/ids/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses',
-                          'method' => 'followersIDs'));
-
-        foreach (array('xml', 'json') as $e) {
-            $m->connect('api/followers/ids.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'statuses',
-                              'method' => 'followersIDs.'.$e));
-        }
+        $m->connect('api/followers/ids/:id.:format',
+                    array('action' => 'apiFollowers',
+                          'ids_only' => true));
+
+        $m->connect('api/friends/ids.:format',
+                    array('action' => 'apiFriends',
+                          'ids_only' => true));
+
+        $m->connect('api/followers/ids.:format',
+                     array('action' => 'apiFollowers',
+                          'ids_only' => true));
 
         // account
 
-        $m->connect('api/account/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'account'));
+        $m->connect('api/account/verify_credentials.:format',
+                    array('action' => 'ApiAccountVerifyCredentials'));
+
+        // special case where verify_credentials is called w/out a format
+
+        $m->connect('api/account/verify_credentials',
+                    array('action' => 'ApiAccountVerifyCredentials'));
+
+        $m->connect('api/account/rate_limit_status.:format',
+                    array('action' => 'ApiAccountRateLimitStatus'));
 
         // favorites
 
-        $m->connect('api/favorites/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'favorites',
-                          array('method' => '(create|destroy)')));
+        $m->connect('api/favorites.:format',
+                    array('action' => 'ApiTimelineFavorites',
+                    'format' => '(xml|json|rss|atom)'));
 
-        $m->connect('api/favorites/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'favorites',
-                          'method' => 'favorites'));
-
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/favorites.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'favorites',
-                              'method' => 'favorites.'.$e));
-        }
+        $m->connect('api/favorites/:id.:format',
+                    array('action' => 'ApiTimelineFavorites',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xmljson|rss|atom)'));
+
+        $m->connect('api/favorites/create/:id.:format',
+                    array('action' => 'ApiFavoriteCreate',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/favorites/destroy/:id.:format',
+                    array('action' => 'ApiFavoriteDestroy',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
 
         // notifications
 
@@ -390,71 +474,112 @@ class Router
 
         // blocks
 
-        $m->connect('api/blocks/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'blocks'));
+        $m->connect('api/blocks/create/:id.:format',
+                    array('action' => 'ApiBlockCreate',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
 
+        $m->connect('api/blocks/destroy/:id.:format',
+                    array('action' => 'ApiBlockDestroy',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
         // help
 
-        $m->connect('api/help/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'help'));
+        $m->connect('api/help/test.:format',
+                    array('action' => 'ApiHelpTest',
+                          'format' => '(xml|json)'));
 
         // statusnet
 
-        $m->connect('api/statusnet/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statusnet'));
+        $m->connect('api/statusnet/version.:format',
+                    array('action' => 'ApiStatusnetVersion',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/config.:format',
+                    array('action' => 'ApiStatusnetConfig',
+                   'format' => '(xml|json)'));
 
         // For older methods, we provide "laconica" base action
 
-        $m->connect('api/laconica/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statusnet'));
+        $m->connect('api/laconica/version.:format',
+                    array('action' => 'ApiStatusnetVersion',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/laconica/config.:format',
+                    array('action' => 'ApiStatusnetConfig',
+                    'format' => '(xml|json)'));
 
         // Groups and tags are newer than 0.8.1 so no backward-compatibility
         // necessary
 
         // Groups
         //'list' has to be handled differently, as php will not allow a method to be named 'list'
-        $m->connect('api/statusnet/groups/list/:argument',
-                    array('action' => 'api',
-                          'method' => 'list_groups',
-                          'apiaction' => 'groups'));
-
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/statusnet/groups/list.' . $e,
-                    array('action' => 'api',
-                          'method' => 'list_groups.' . $e,
-                          'apiaction' => 'groups'));
-        }
-
-        $m->connect('api/statusnet/groups/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(list_all|)(\.(atom|rss|xml|json))?'));
-
-        $m->connect('api/statuses/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
-
-        $m->connect('api/statusnet/groups/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'groups'));
-
-        $m->connect('api/statusnet/groups/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'groups'));
 
+        $m->connect('api/statusnet/groups/timeline/:id.:format',
+                    array('action' => 'ApiTimelineGroup',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xmljson|rss|atom)'));
+
+        $m->connect('api/statusnet/groups/show.:format',
+                    array('action' => 'ApiGroupShow',
+                    'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/show/:id.:format',
+                    array('action' => 'ApiGroupShow',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/join.:format',
+                    array('action' => 'ApiGroupJoin',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/join/:id.:format',
+                    array('action' => 'ApiGroupJoin',
+                    'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/leave.:format',
+                    array('action' => 'ApiGroupLeave',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/leave/:id.:format',
+                    array('action' => 'ApiGroupLeave',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/is_member.:format',
+                    array('action' => 'ApiGroupIsMember',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/list.:format',
+                    array('action' => 'ApiGroupList',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statusnet/groups/list/:id.:format',
+                    array('action' => 'ApiGroupList',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statusnet/groups/list_all.:format',
+                    array('action' => 'ApiGroupListAll',
+                          'format' => '(xml|json|rss|atom)'));
+
+        $m->connect('api/statusnet/groups/membership.:format',
+                    array('action' => 'ApiGroupMembership',
+                         'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/groups/membership/:id.:format',
+                    array('action' => 'ApiGroupMembership',
+                           'id' => '[a-zA-Z0-9]+',
+                           'format' => '(xml|json)'));
+                           
+        $m->connect('api/statusnet/groups/create.:format',
+                    array('action' => 'ApiGroupCreate',
+                          'format' => '(xml|json)'));
         // Tags
-        $m->connect('api/statusnet/tags/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'tags'));
-
-        $m->connect('api/statusnet/tags/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'tags'));
+        $m->connect('api/statusnet/tags/timeline/:tag.:format',
+                    array('action' => 'ApiTimelineTag',
+                          'format' => '(xmljson|rss|atom)'));
 
         // search
         $m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
index b49e2e11902997bdfb1681a48729361397f956f1..afc3f55bab932614744ef43732485348f53c7eba 100644 (file)
@@ -23,7 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
 
 define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
 
-function update_twitter_user($twitter_id, $screen_name)
+function updateTwitter_user($twitter_id, $screen_name)
 {
     $uri = 'http://twitter.com/' . $screen_name;
     $fuser = new Foreign_user();
@@ -115,7 +115,7 @@ function save_twitter_user($twitter_id, $screen_name)
         // Only update if Twitter screen name has changed
 
         if ($fuser->nickname != $screen_name) {
-            $result = update_twitter_user($twitter_id, $screen_name);
+            $result = updateTwitter_user($twitter_id, $screen_name);
 
             common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
                 "$fuser->id to $screen_name, was $fuser->nickname");
diff --git a/lib/twitterapi.php b/lib/twitterapi.php
deleted file mode 100644 (file)
index 4a5de6a..0000000
+++ /dev/null
@@ -1,1183 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, 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/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-class TwitterapiAction extends Action
-{
-
-    var $auth_user;
-
-    /**
-     * Initialization.
-     *
-     * @param array $args Web and URL arguments
-     *
-     * @return boolean false if user doesn't exist
-     */
-
-    function prepare($args)
-    {
-        parent::prepare($args);
-        return true;
-    }
-
-    /**
-     * Handle a request
-     *
-     * @param array $args Arguments from $_REQUEST
-     *
-     * @return void
-     */
-
-    function handle($args)
-    {
-        parent::handle($args);
-    }
-
-    /**
-     * Overrides XMLOutputter::element to write booleans as strings (true|false).
-     * See that method's documentation for more info.
-     *
-     * @param string $tag     Element type or tagname
-     * @param array  $attrs   Array of element attributes, as
-     *                        key-value pairs
-     * @param string $content string content of the element
-     *
-     * @return void
-     */
-    function element($tag, $attrs=null, $content=null)
-    {
-        if (is_bool($content)) {
-            $content = ($content ? 'true' : 'false');
-        }
-
-        return parent::element($tag, $attrs, $content);
-    }
-
-    function twitter_user_array($profile, $get_notice=false)
-    {
-        $twitter_user = array();
-
-        $twitter_user['id'] = intval($profile->id);
-        $twitter_user['name'] = $profile->getBestName();
-        $twitter_user['screen_name'] = $profile->nickname;
-        $twitter_user['location'] = ($profile->location) ? $profile->location : null;
-        $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
-
-        $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
-        $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
-            Avatar::defaultImage(AVATAR_STREAM_SIZE);
-
-        $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
-        $twitter_user['protected'] = false; # not supported by StatusNet yet
-        $twitter_user['followers_count'] = $profile->subscriberCount();
-
-        // To be supported soon...
-        $twitter_user['profile_background_color'] = '';
-        $twitter_user['profile_text_color'] = '';
-        $twitter_user['profile_link_color'] = '';
-        $twitter_user['profile_sidebar_fill_color'] = '';
-        $twitter_user['profile_sidebar_border_color'] = '';
-
-        $twitter_user['friends_count'] = $profile->subscriptionCount();
-
-        $twitter_user['created_at'] = $this->date_twitter($profile->created);
-
-        $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
-
-        // Need to pull up the user for some of this
-        $user = User::staticGet($profile->id);
-
-        $timezone = 'UTC';
-
-        if ($user->timezone) {
-            $timezone = $user->timezone;
-        }
-
-        $t = new DateTime;
-        $t->setTimezone(new DateTimeZone($timezone));
-
-        $twitter_user['utc_offset'] = $t->format('Z');
-        $twitter_user['time_zone'] = $timezone;
-
-        // To be supported some day, perhaps
-        $twitter_user['profile_background_image_url'] = '';
-        $twitter_user['profile_background_tile'] = false;
-
-        $twitter_user['statuses_count'] = $profile->noticeCount();
-
-        // Is the requesting user following this user?
-        $twitter_user['following'] = false;
-        $twitter_user['notifications'] = false;
-
-        if (isset($apidata['user'])) {
-
-            $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
-
-            // Notifications on?
-            $sub = Subscription::pkeyGet(array('subscriber' =>
-                $apidata['user']->id, 'subscribed' => $profile->id));
-
-            if ($sub) {
-                $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
-            }
-        }
-
-        if ($get_notice) {
-            $notice = $profile->getCurrentNotice();
-            if ($notice) {
-                # don't get user!
-                $twitter_user['status'] = $this->twitter_status_array($notice, false);
-            }
-        }
-
-        return $twitter_user;
-    }
-
-    function twitter_status_array($notice, $include_user=true)
-    {
-        $profile = $notice->getProfile();
-
-        $twitter_status = array();
-        $twitter_status['text'] = $notice->content;
-        $twitter_status['truncated'] = false; # Not possible on StatusNet
-        $twitter_status['created_at'] = $this->date_twitter($notice->created);
-        $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
-            intval($notice->reply_to) : null;
-        $twitter_status['source'] = $this->source_link($notice->source);
-        $twitter_status['id'] = intval($notice->id);
-
-        $replier_profile = null;
-
-        if ($notice->reply_to) {
-            $reply = Notice::staticGet(intval($notice->reply_to));
-            if ($reply) {
-                $replier_profile = $reply->getProfile();
-            }
-        }
-
-        $twitter_status['in_reply_to_user_id'] =
-            ($replier_profile) ? intval($replier_profile->id) : null;
-        $twitter_status['in_reply_to_screen_name'] =
-            ($replier_profile) ? $replier_profile->nickname : null;
-
-        if (isset($this->auth_user)) {
-            $twitter_status['favorited'] = $this->auth_user->hasFave($notice);
-        } else {
-            $twitter_status['favorited'] = false;
-        }
-
-        // Enclosures
-        $attachments = $notice->attachments();
-
-        if (!empty($attachments)) {
-
-            $twitter_status['attachments'] = array();
-
-            foreach ($attachments as $attachment) {
-                if ($attachment->isEnclosure()) {
-                    $enclosure = array();
-                    $enclosure['url'] = $attachment->url;
-                    $enclosure['mimetype'] = $attachment->mimetype;
-                    $enclosure['size'] = $attachment->size;
-                    $twitter_status['attachments'][] = $enclosure;
-                }
-            }
-        }
-
-        if ($include_user) {
-            # Don't get notice (recursive!)
-            $twitter_user = $this->twitter_user_array($profile, false);
-            $twitter_status['user'] = $twitter_user;
-        }
-
-        return $twitter_status;
-    }
-
-    function twitter_group_array($group)
-    {
-        $twitter_group=array();
-        $twitter_group['id']=$group->id;
-        $twitter_group['url']=$group->permalink();
-        $twitter_group['nickname']=$group->nickname;
-        $twitter_group['fullname']=$group->fullname;
-        $twitter_group['homepage_url']=$group->homepage_url;
-        $twitter_group['original_logo']=$group->original_logo;
-        $twitter_group['homepage_logo']=$group->homepage_logo;
-        $twitter_group['stream_logo']=$group->stream_logo;
-        $twitter_group['mini_logo']=$group->mini_logo;
-        $twitter_group['homepage']=$group->homepage;
-        $twitter_group['description']=$group->description;
-        $twitter_group['location']=$group->location;
-        $twitter_group['created']=$this->date_twitter($group->created);
-        $twitter_group['modified']=$this->date_twitter($group->modified);
-        return $twitter_group;
-    }
-
-    function twitter_rss_group_array($group)
-    {
-        $entry = array();
-        $entry['content']=$group->description;
-        $entry['title']=$group->nickname;
-        $entry['link']=$group->permalink();
-        $entry['published']=common_date_iso8601($group->created);
-        $entry['updated']==common_date_iso8601($group->modified);
-        $taguribase = common_config('integration', 'groupuri');
-        $entry['id'] = "group:$groupuribase:$entry[link]";
-
-        $entry['description'] = $entry['content'];
-        $entry['pubDate'] = common_date_rfc2822($group->created);
-        $entry['guid'] = $entry['link'];
-
-        return $entry;
-    }
-
-    function twitter_rss_entry_array($notice)
-    {
-        $profile = $notice->getProfile();
-        $entry = array();
-
-        // We trim() to avoid extraneous whitespace in the output
-
-        $entry['content'] = common_xml_safe_str(trim($notice->rendered));
-        $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
-        $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
-        $entry['published'] = common_date_iso8601($notice->created);
-
-        $taguribase = common_config('integration', 'taguri');
-        $entry['id'] = "tag:$taguribase:$entry[link]";
-
-        $entry['updated'] = $entry['published'];
-        $entry['author'] = $profile->getBestName();
-
-        // Enclosures
-        $attachments = $notice->attachments();
-        $enclosures = array();
-
-        foreach ($attachments as $attachment) {
-            $enclosure_o=$attachment->getEnclosure();
-            if ($enclosure_o) {
-                 $enclosure = array();
-                 $enclosure['url'] = $enclosure_o->url;
-                 $enclosure['mimetype'] = $enclosure_o->mimetype;
-                 $enclosure['size'] = $enclosure_o->size;
-                 $enclosures[] = $enclosure;
-            }
-        }
-
-        if (!empty($enclosures)) {
-            $entry['enclosures'] = $enclosures;
-        }
-
-/*
-        // Enclosure
-        $attachments = $notice->attachments();
-        if($attachments){
-            $entry['enclosures']=array();
-            foreach($attachments as $attachment){
-                if ($attachment->isEnclosure()) {
-                    $enclosure=array();
-                    $enclosure['url']=$attachment->url;
-                    $enclosure['mimetype']=$attachment->mimetype;
-                    $enclosure['size']=$attachment->size;
-                    $entry['enclosures'][]=$enclosure;
-                }
-            }
-        }
-*/
-
-        // Tags/Categories
-        $tag = new Notice_tag();
-        $tag->notice_id = $notice->id;
-        if ($tag->find()) {
-            $entry['tags']=array();
-            while ($tag->fetch()) {
-                $entry['tags'][]=$tag->tag;
-            }
-        }
-        $tag->free();
-
-        // RSS Item specific
-        $entry['description'] = $entry['content'];
-        $entry['pubDate'] = common_date_rfc2822($notice->created);
-        $entry['guid'] = $entry['link'];
-
-        return $entry;
-    }
-
-    function twitter_rss_dmsg_array($message)
-    {
-
-        $entry = array();
-
-        $entry['title'] = sprintf('Message from %s to %s',
-            $message->getFrom()->nickname, $message->getTo()->nickname);
-
-        $entry['content'] = common_xml_safe_str(trim($message->content));
-        $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
-        $entry['published'] = common_date_iso8601($message->created);
-
-        $taguribase = common_config('integration', 'taguri');
-
-        $entry['id'] = "tag:$taguribase,:$entry[link]";
-        $entry['updated'] = $entry['published'];
-        $entry['author'] = $message->getFrom()->getBestName();
-
-        # RSS Item specific
-        $entry['description'] = $entry['content'];
-        $entry['pubDate'] = common_date_rfc2822($message->created);
-        $entry['guid'] = $entry['link'];
-
-        return $entry;
-    }
-
-    function twitter_dmsg_array($message)
-    {
-        $twitter_dm = array();
-
-        $from_profile = $message->getFrom();
-        $to_profile = $message->getTo();
-
-        $twitter_dm['id'] = $message->id;
-        $twitter_dm['sender_id'] = $message->from_profile;
-        $twitter_dm['text'] = trim($message->content);
-        $twitter_dm['recipient_id'] = $message->to_profile;
-        $twitter_dm['created_at'] = $this->date_twitter($message->created);
-        $twitter_dm['sender_screen_name'] = $from_profile->nickname;
-        $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
-        $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
-        $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
-
-        return $twitter_dm;
-    }
-
-    function twitter_relationship_array($source, $target)
-    {
-        $relationship = array();
-
-        $relationship['source'] =
-            $this->relationship_details_array($source, $target);
-        $relationship['target'] =
-            $this->relationship_details_array($target, $source);
-
-        return array('relationship' => $relationship);
-    }
-
-    function relationship_details_array($source, $target)
-    {
-        $details = array();
-
-        $details['screen_name'] = $source->nickname;
-        $details['followed_by'] = $target->isSubscribed($source);
-        $details['following'] = $source->isSubscribed($target);
-
-        $notifications = false;
-
-        if ($source->isSubscribed($target)) {
-
-            $sub = Subscription::pkeyGet(array('subscriber' =>
-                $source->id, 'subscribed' => $target->id));
-
-            if (!empty($sub)) {
-                $notifications = ($sub->jabber || $sub->sms);
-            }
-        }
-
-        $details['notifications_enabled'] = $notifications;
-        $details['blocking'] = $source->hasBlocked($target);
-        $details['id'] = $source->id;
-
-        return $details;
-    }
-
-    function show_twitter_xml_relationship($relationship)
-    {
-        $this->elementStart('relationship');
-
-        foreach($relationship as $element => $value) {
-            if ($element == 'source' || $element == 'target') {
-                $this->elementStart($element);
-                $this->show_xml_relationship_details($value);
-                $this->elementEnd($element);
-            }
-        }
-
-        $this->elementEnd('relationship');
-    }
-
-    function show_xml_relationship_details($details)
-    {
-        foreach($details as $element => $value) {
-            $this->element($element, null, $value);
-        }
-    }
-
-    function show_twitter_xml_status($twitter_status)
-    {
-        $this->elementStart('status');
-        foreach($twitter_status as $element => $value) {
-            switch ($element) {
-            case 'user':
-                $this->show_twitter_xml_user($twitter_status['user']);
-                break;
-            case 'text':
-                $this->element($element, null, common_xml_safe_str($value));
-                break;
-            case 'attachments':
-                $this->show_xml_attachments($twitter_status['attachments']);
-                break;
-            default:
-                $this->element($element, null, $value);
-            }
-        }
-        $this->elementEnd('status');
-    }
-
-    function show_twitter_xml_group($twitter_group)
-    {
-        $this->elementStart('group');
-        foreach($twitter_group as $element => $value) {
-            $this->element($element, null, $value);
-        }
-        $this->elementEnd('group');
-    }
-
-    function show_twitter_xml_user($twitter_user, $role='user')
-    {
-        $this->elementStart($role);
-        foreach($twitter_user as $element => $value) {
-            if ($element == 'status') {
-                $this->show_twitter_xml_status($twitter_user['status']);
-            } else {
-                $this->element($element, null, $value);
-            }
-        }
-        $this->elementEnd($role);
-    }
-
-    function show_xml_attachments($attachments) {
-        if (!empty($attachments)) {
-            $this->elementStart('attachments', array('type' => 'array'));
-            foreach ($attachments as $attachment) {
-                $attrs = array();
-                $attrs['url'] = $attachment['url'];
-                $attrs['mimetype'] = $attachment['mimetype'];
-                $attrs['size'] = $attachment['size'];
-                $this->element('enclosure', $attrs, '');
-            }
-            $this->elementEnd('attachments');
-        }
-    }
-
-    function show_twitter_rss_item($entry)
-    {
-        $this->elementStart('item');
-        $this->element('title', null, $entry['title']);
-        $this->element('description', null, $entry['description']);
-        $this->element('pubDate', null, $entry['pubDate']);
-        $this->element('guid', null, $entry['guid']);
-        $this->element('link', null, $entry['link']);
-
-        # RSS only supports 1 enclosure per item
-        if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
-            $enclosure = $entry['enclosures'][0];
-            $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
-        }
-
-        if(array_key_exists('tags', $entry)){
-            foreach($entry['tags'] as $tag){
-                $this->element('category', null,$tag);
-            }
-        }
-
-        $this->elementEnd('item');
-    }
-
-    function show_json_objects($objects)
-    {
-        print(json_encode($objects));
-    }
-
-    function show_single_xml_status($notice)
-    {
-        $this->init_document('xml');
-        $twitter_status = $this->twitter_status_array($notice);
-        $this->show_twitter_xml_status($twitter_status);
-        $this->end_document('xml');
-    }
-
-    function show_single_json_status($notice)
-    {
-        $this->init_document('json');
-        $status = $this->twitter_status_array($notice);
-        $this->show_json_objects($status);
-        $this->end_document('json');
-    }
-
-    function show_single_xml_dmsg($message)
-    {
-        $this->init_document('xml');
-        $dmsg = $this->twitter_dmsg_array($message);
-        $this->show_twitter_xml_dmsg($dmsg);
-        $this->end_document('xml');
-    }
-
-    function show_single_json_dmsg($message)
-    {
-        $this->init_document('json');
-        $dmsg = $this->twitter_dmsg_array($message);
-        $this->show_json_objects($dmsg);
-        $this->end_document('json');
-    }
-
-    function show_twitter_xml_dmsg($twitter_dm)
-    {
-        $this->elementStart('direct_message');
-        foreach($twitter_dm as $element => $value) {
-            switch ($element) {
-            case 'sender':
-            case 'recipient':
-                $this->show_twitter_xml_user($value, $element);
-                break;
-            case 'text':
-                $this->element($element, null, common_xml_safe_str($value));
-                break;
-            default:
-                $this->element($element, null, $value);
-            }
-        }
-        $this->elementEnd('direct_message');
-    }
-
-    function show_xml_timeline($notice)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('statuses', array('type' => 'array'));
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $twitter_status = $this->twitter_status_array($n);
-                $this->show_twitter_xml_status($twitter_status);
-            }
-        } else {
-            while ($notice->fetch()) {
-                $twitter_status = $this->twitter_status_array($notice);
-                $this->show_twitter_xml_status($twitter_status);
-            }
-        }
-
-        $this->elementEnd('statuses');
-        $this->end_document('xml');
-    }
-
-    function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
-    {
-
-        $this->init_document('rss');
-
-        $this->element('title', null, $title);
-        $this->element('link', null, $link);
-        if (!is_null($suplink)) {
-            # For FriendFeed's SUP protocol
-            $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
-                                         'rel' => 'http://api.friendfeed.com/2008/03#sup',
-                                         'href' => $suplink,
-                                         'type' => 'application/json'));
-        }
-        $this->element('description', null, $subtitle);
-        $this->element('language', null, 'en-us');
-        $this->element('ttl', null, '40');
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $entry = $this->twitter_rss_entry_array($n);
-                $this->show_twitter_rss_item($entry);
-            }
-        } else {
-            while ($notice->fetch()) {
-                $entry = $this->twitter_rss_entry_array($notice);
-                $this->show_twitter_rss_item($entry);
-            }
-        }
-
-        $this->end_twitter_rss();
-    }
-
-    function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
-    {
-
-        $this->init_document('atom');
-
-        $this->element('title', null, $title);
-        $this->element('id', null, $id);
-        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
-
-        if (!is_null($suplink)) {
-            # For FriendFeed's SUP protocol
-            $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
-                                         'href' => $suplink,
-                                         'type' => 'application/json'));
-        }
-
-        if (!is_null($selfuri)) {
-            $this->element('link', array('href' => $selfuri,
-                'rel' => 'self', 'type' => 'application/atom+xml'), null);
-        }
-
-        $this->element('updated', null, common_date_iso8601('now'));
-        $this->element('subtitle', null, $subtitle);
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $this->raw($n->asAtomEntry());
-            }
-        } else {
-            while ($notice->fetch()) {
-                $this->raw($notice->asAtomEntry());
-            }
-        }
-
-        $this->end_document('atom');
-
-    }
-
-    function show_rss_groups($group, $title, $link, $subtitle)
-    {
-
-        $this->init_document('rss');
-
-        $this->element('title', null, $title);
-        $this->element('link', null, $link);
-        $this->element('description', null, $subtitle);
-        $this->element('language', null, 'en-us');
-        $this->element('ttl', null, '40');
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $twitter_group = $this->twitter_rss_group_array($g);
-                $this->show_twitter_rss_item($twitter_group);
-            }
-        } else {
-            while ($group->fetch()) {
-                $twitter_group = $this->twitter_rss_group_array($group);
-                $this->show_twitter_rss_item($twitter_group);
-            }
-        }
-
-        $this->end_twitter_rss();
-    }
-
-    function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
-    {
-
-        $this->init_document('atom');
-
-        $this->element('title', null, $title);
-        $this->element('id', null, $id);
-        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
-
-        if (!is_null($selfuri)) {
-            $this->element('link', array('href' => $selfuri,
-                'rel' => 'self', 'type' => 'application/atom+xml'), null);
-        }
-
-        $this->element('updated', null, common_date_iso8601('now'));
-        $this->element('subtitle', null, $subtitle);
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $this->raw($g->asAtomEntry());
-            }
-        } else {
-            while ($group->fetch()) {
-                $this->raw($group->asAtomEntry());
-            }
-        }
-
-        $this->end_document('atom');
-
-    }
-
-    function show_json_timeline($notice)
-    {
-
-        $this->init_document('json');
-
-        $statuses = array();
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $twitter_status = $this->twitter_status_array($n);
-                array_push($statuses, $twitter_status);
-            }
-        } else {
-            while ($notice->fetch()) {
-                $twitter_status = $this->twitter_status_array($notice);
-                array_push($statuses, $twitter_status);
-            }
-        }
-
-        $this->show_json_objects($statuses);
-
-        $this->end_document('json');
-    }
-
-    function show_json_groups($group)
-    {
-
-        $this->init_document('json');
-
-        $groups = array();
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $twitter_group = $this->twitter_group_array($g);
-                array_push($groups, $twitter_group);
-            }
-        } else {
-            while ($group->fetch()) {
-                $twitter_group = $this->twitter_group_array($group);
-                array_push($groups, $twitter_group);
-            }
-        }
-
-        $this->show_json_objects($groups);
-
-        $this->end_document('json');
-    }
-
-    function show_xml_groups($group)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('groups', array('type' => 'array'));
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $twitter_group = $this->twitter_group_array($g);
-                $this->show_twitter_xml_group($twitter_group);
-            }
-        } else {
-            while ($group->fetch()) {
-                $twitter_group = $this->twitter_group_array($group);
-                $this->show_twitter_xml_group($twitter_group);
-            }
-        }
-
-        $this->elementEnd('groups');
-        $this->end_document('xml');
-    }
-
-    function show_twitter_xml_users($user)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('users', array('type' => 'array'));
-
-        if (is_array($user)) {
-            foreach ($group as $g) {
-                $twitter_user = $this->twitter_user_array($g);
-                $this->show_twitter_xml_user($twitter_user,'user');
-            }
-        } else {
-            while ($user->fetch()) {
-                $twitter_user = $this->twitter_user_array($user);
-                $this->show_twitter_xml_user($twitter_user);
-            }
-        }
-
-        $this->elementEnd('users');
-        $this->end_document('xml');
-    }
-
-    function show_json_users($user)
-    {
-
-        $this->init_document('json');
-
-        $users = array();
-
-        if (is_array($user)) {
-            foreach ($user as $u) {
-                $twitter_user = $this->twitter_user_array($u);
-                array_push($users, $twitter_user);
-            }
-        } else {
-            while ($user->fetch()) {
-                $twitter_user = $this->twitter_user_array($user);
-                array_push($users, $twitter_user);
-            }
-        }
-
-        $this->show_json_objects($users);
-
-        $this->end_document('json');
-    }
-
-    function show_single_json_group($group)
-    {
-        $this->init_document('json');
-        $twitter_group = $this->twitter_group_array($group);
-        $this->show_json_objects($twitter_group);
-        $this->end_document('json');
-    }
-
-    function show_single_xml_group($group)
-    {
-        $this->init_document('xml');
-        $twitter_group = $this->twitter_group_array($group);
-        $this->show_twitter_xml_group($twitter_group);
-        $this->end_document('xml');
-    }
-
-    // Anyone know what date format this is?
-    // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
-    function date_twitter($dt)
-    {
-        $t = strtotime($dt);
-        return date("D M d H:i:s O Y", $t);
-    }
-
-    // XXX: Candidate for a general utility method somewhere?
-    function count_subscriptions($profile)
-    {
-
-        $count = 0;
-        $sub = new Subscription();
-        $sub->subscribed = $profile->id;
-
-        $count = $sub->find();
-
-        if ($count > 0) {
-            return $count - 1;
-        } else {
-            return 0;
-        }
-    }
-
-    function init_document($type='xml')
-    {
-        switch ($type) {
-        case 'xml':
-            header('Content-Type: application/xml; charset=utf-8');
-            $this->startXML();
-            break;
-        case 'json':
-            header('Content-Type: application/json; charset=utf-8');
-
-            // Check for JSONP callback
-            $callback = $this->arg('callback');
-            if ($callback) {
-                print $callback . '(';
-            }
-            break;
-        case 'rss':
-            header("Content-Type: application/rss+xml; charset=utf-8");
-            $this->init_twitter_rss();
-            break;
-        case 'atom':
-            header('Content-Type: application/atom+xml; charset=utf-8');
-            $this->init_twitter_atom();
-            break;
-        default:
-            $this->clientError(_('Not a supported data format.'));
-            break;
-        }
-
-        return;
-    }
-
-    function end_document($type='xml')
-    {
-        switch ($type) {
-        case 'xml':
-            $this->endXML();
-            break;
-        case 'json':
-
-            // Check for JSONP callback
-            $callback = $this->arg('callback');
-            if ($callback) {
-                print ')';
-            }
-            break;
-        case 'rss':
-            $this->end_twitter_rss();
-            break;
-        case 'atom':
-            $this->end_twitter_rss();
-            break;
-        default:
-            $this->clientError(_('Not a supported data format.'));
-            break;
-        }
-        return;
-    }
-
-    function clientError($msg, $code = 400, $content_type = 'json')
-    {
-        $action = $this->trimmed('action');
-
-        common_debug("User error '$code' on '$action': $msg", __FILE__);
-
-        if (!array_key_exists($code, ClientErrorAction::$status)) {
-            $code = 400;
-        }
-
-        $status_string = ClientErrorAction::$status[$code];
-
-        header('HTTP/1.1 '.$code.' '.$status_string);
-
-        if ($content_type == 'xml') {
-            $this->init_document('xml');
-            $this->elementStart('hash');
-            $this->element('error', null, $msg);
-            $this->element('request', null, $_SERVER['REQUEST_URI']);
-            $this->elementEnd('hash');
-            $this->end_document('xml');
-        } else {
-            $this->init_document('json');
-            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
-            print(json_encode($error_array));
-            $this->end_document('json');
-        }
-
-    }
-
-    function serverError($msg, $code = 500, $content_type = 'json')
-    {
-        $action = $this->trimmed('action');
-
-        common_debug("Server error '$code' on '$action': $msg", __FILE__);
-
-        if (!array_key_exists($code, ServerErrorAction::$status)) {
-            $code = 400;
-        }
-
-        $status_string = ServerErrorAction::$status[$code];
-
-        header('HTTP/1.1 '.$code.' '.$status_string);
-
-        if ($content_type == 'xml') {
-            $this->init_document('xml');
-            $this->elementStart('hash');
-            $this->element('error', null, $msg);
-            $this->element('request', null, $_SERVER['REQUEST_URI']);
-            $this->elementEnd('hash');
-            $this->end_document('xml');
-        } else {
-            $this->init_document('json');
-            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
-            print(json_encode($error_array));
-            $this->end_document('json');
-        }
-    }
-
-    function init_twitter_rss()
-    {
-        $this->startXML();
-        $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom'));
-        $this->elementStart('channel');
-        Event::handle('StartApiRss', array($this));
-    }
-
-    function end_twitter_rss()
-    {
-        $this->elementEnd('channel');
-        $this->elementEnd('rss');
-        $this->endXML();
-    }
-
-    function init_twitter_atom()
-    {
-        $this->startXML();
-        // FIXME: don't hardcode the language here!
-        $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
-                                          'xml:lang' => 'en-US',
-                                          'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
-        Event::handle('StartApiAtom', array($this));
-    }
-
-    function end_twitter_atom()
-    {
-        $this->elementEnd('feed');
-        $this->endXML();
-    }
-
-    function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
-    {
-        $profile_array = $this->twitter_user_array($profile, $includeStatuses);
-        switch ($content_type) {
-        case 'xml':
-            $this->show_twitter_xml_user($profile_array);
-            break;
-        case 'json':
-            $this->show_json_objects($profile_array);
-            break;
-        default:
-            $this->clientError(_('Not a supported data format.'));
-            return;
-        }
-        return;
-    }
-
-    function get_user($id, $apidata=null)
-    {
-        if (empty($id)) {
-
-            // Twitter supports these other ways of passing the user ID
-            if (is_numeric($this->arg('id'))) {
-                return User::staticGet($this->arg('id'));
-            } else if ($this->arg('id')) {
-                $nickname = common_canonical_nickname($this->arg('id'));
-                return User::staticGet('nickname', $nickname);
-            } else if ($this->arg('user_id')) {
-                // This is to ensure that a non-numeric user_id still
-                // overrides screen_name even if it doesn't get used
-                if (is_numeric($this->arg('user_id'))) {
-                    return User::staticGet('id', $this->arg('user_id'));
-                }
-            } else if ($this->arg('screen_name')) {
-                $nickname = common_canonical_nickname($this->arg('screen_name'));
-                return User::staticGet('nickname', $nickname);
-            } else {
-                // Fall back to trying the currently authenticated user
-                return $apidata['user'];
-            }
-
-        } else if (is_numeric($id)) {
-            return User::staticGet($id);
-        } else {
-            $nickname = common_canonical_nickname($id);
-            return User::staticGet('nickname', $nickname);
-        }
-    }
-
-    function get_group($id, $apidata=null)
-    {
-        if (empty($id)) {
-
-            if (is_numeric($this->arg('id'))) {
-                return User_group::staticGet($this->arg('id'));
-            } else if ($this->arg('id')) {
-                $nickname = common_canonical_nickname($this->arg('id'));
-                return User_group::staticGet('nickname', $nickname);
-            } else if ($this->arg('group_id')) {
-                // This is to ensure that a non-numeric user_id still
-                // overrides screen_name even if it doesn't get used
-                if (is_numeric($this->arg('group_id'))) {
-                    return User_group::staticGet('id', $this->arg('group_id'));
-                }
-            } else if ($this->arg('group_name')) {
-                $nickname = common_canonical_nickname($this->arg('group_name'));
-                return User_group::staticGet('nickname', $nickname);
-            }
-
-        } else if (is_numeric($id)) {
-            return User_group::staticGet($id);
-        } else {
-            $nickname = common_canonical_nickname($id);
-            return User_group::staticGet('nickname', $nickname);
-        }
-    }
-
-    function get_profile($id)
-    {
-        if (is_numeric($id)) {
-            return Profile::staticGet($id);
-        } else {
-            $user = User::staticGet('nickname', $id);
-            if ($user) {
-                return $user->getProfile();
-            } else {
-                return null;
-            }
-        }
-    }
-
-    function source_link($source)
-    {
-        $source_name = _($source);
-        switch ($source) {
-        case 'web':
-        case 'xmpp':
-        case 'mail':
-        case 'omb':
-        case 'api':
-            break;
-        default:
-            $ns = Notice_source::staticGet($source);
-            if ($ns) {
-                $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
-            }
-            break;
-        }
-        return $source_name;
-    }
-
-    /**
-     * Returns query argument or default value if not found. Certain
-     * parameters used throughout the API are lightly scrubbed and
-     * bounds checked.  This overrides Action::arg().
-     *
-     * @param string $key requested argument
-     * @param string $def default value to return if $key is not provided
-     *
-     * @return var $var
-     */
-    function arg($key, $def=null)
-    {
-
-        // XXX: Do even more input validation/scrubbing?
-
-        if (array_key_exists($key, $this->args)) {
-            switch($key) {
-            case 'page':
-                $page = (int)$this->args['page'];
-                return ($page < 1) ? 1 : $page;
-            case 'count':
-                $count = (int)$this->args['count'];
-                if ($count < 1) {
-                    return 20;
-                } elseif ($count > 200) {
-                    return 200;
-                } else {
-                    return $count;
-                }
-            case 'since_id':
-                $since_id = (int)$this->args['since_id'];
-                return ($since_id < 1) ? 0 : $since_id;
-            case 'max_id':
-                $max_id = (int)$this->args['max_id'];
-                return ($max_id < 1) ? 0 : $max_id;
-            case 'since':
-                return strtotime($this->args['since']);
-            default:
-                return parent::arg($key, $def);
-            }
-        } else {
-            return $def;
-        }
-    }
-
-}
index fd331fbdc90966289abd9d5c342eb34c8280adf0..1040d72fb6229d1bbc8a78c969bfd464eb0119e2 100644 (file)
@@ -36,8 +36,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  *
  * @category Integration
  * @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
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @author Brenda Wallace <shiny@cpan.org>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Dan Moore <dan@moore.cx>
+ * @author Evan Prodromou <evan@status.net>
+ * @author mEDI <medi@milaro.net>
+ * @author Sarven Capadisli <csarven@status.net>
+ * @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/
  *
  */
index 181927968648a132f2ef9754725502f3b8d4b5af..0c7c1240c3f7bb24031e7db82cdc33c7acbb8c04 100644 (file)
@@ -240,13 +240,13 @@ class RealtimePlugin extends Plugin
         // FIXME: this code should be abstracted to a neutral third
         // party, like Notice::asJson(). I'm not sure of the ethics
         // of refactoring from within a plugin, so I'm just abusing
-        // the TwitterApiAction method. Don't do this unless you're me!
+        // the ApiAction method. Don't do this unless you're me!
 
-        require_once(INSTALLDIR.'/lib/twitterapi.php');
+        require_once(INSTALLDIR.'/lib/api.php');
 
-        $act = new TwitterApiAction('/dev/null');
+        $act = new ApiAction('/dev/null');
 
-        $arr = $act->twitter_status_array($notice, true);
+        $arr = $act->twitterStatusArray($notice, true);
         $arr['url'] = $notice->bestUrl();
         $arr['html'] = htmlspecialchars($notice->rendered);
         $arr['source'] = htmlspecialchars($arr['source']);
diff --git a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php
new file mode 100644 (file)
index 0000000..4806538
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin that requires the user to have a validated email address before they can post 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  Plugin
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Craig Andrews http://candrews.integralblue.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') && !defined('LACONICA')) {
+    exit(1);
+}
+
+class RequireValidatedEmailPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function onStartNoticeSave($notice)
+    {
+        $user = User::staticGet('id', $notice->profile_id);
+        if (!empty($user)) { // it's a remote notice
+            if (empty($user->email)) {
+                throw new ClientException(_("You must validate your email address before posting."));
+            }
+        }
+        return true;
+    }
+}
+
index 86369cb9935d64f82f9757f1bdf81a398e5e8742..3993da717751c560316f44e34e44b14bad7d9e59 100644 (file)
@@ -88,7 +88,7 @@ color:#333333;
 color:#000000;
 }
 #form_notice label[for=notice_data-attach] {
-background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px;
 }
 #form_notice #notice_data-attach {
 opacity:0;
@@ -150,16 +150,18 @@ background-color:#9BB43E;
 
 #export_data li a {
 background-repeat:no-repeat;
-background-position:0 45%;
 }
 #export_data li a.rss {
-background-image:url(../../base/images/icons/icon_rss.png);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 -130px;
 }
 #export_data li a.atom {
-background-image:url(../../base/images/icons/icon_atom.png);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 -64px;
 }
 #export_data li a.foaf {
-background-image:url(../../base/images/icons/icon_foaf.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 1px;
 }
 
 .entity_edit a,
@@ -171,7 +173,6 @@ background-image:url(../../base/images/icons/icon_foaf.gif);
 .form_group_unblock input.submit,
 .entity_nudge p,
 .form_make_admin input.submit {
-background-position: 0 40%;
 background-repeat: no-repeat;
 background-color:transparent;
 }
@@ -189,43 +190,48 @@ background-color:#87B4C8;
 }
 
 .entity_edit a {
-background-image:url(../../base/images/icons/twotone/green/edit.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -718px;
 }
 .entity_send-a-message a {
-background-image:url(../../base/images/icons/twotone/green/quote.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -849px;
 }
 .entity_nudge p,
 .form_user_nudge input.submit {
-background-image:url(../../base/images/icons/twotone/green/mail.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -785px;
 }
 .form_user_block input.submit,
 .form_user_unblock input.submit,
 .form_group_block input.submit,
 .form_group_unblock input.submit {
-background-image:url(../../base/images/icons/twotone/green/shield.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -918px;
 }
 .form_make_admin input.submit {
-background-image:url(../../base/images/icons/twotone/green/admin.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -983px;
 }
 
 /* NOTICES */
 .notice .attachment {
-background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -394px;
 }
 #attachments .attachment {
 background:none;
 }
 .notice-options .notice_reply {
-background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -589px;
 }
 .notice-options form.form_favor input.submit {
-background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -457px;
 }
 .notice-options form.form_disfavor input.submit {
-background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -523px;
 }
 .notice-options .notice_delete {
-background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -655px;
 }
 
 .notices div.entry-content,
@@ -262,7 +268,7 @@ background-color:rgba(200, 200, 200, 0.300);
 /*END: NOTICES */
 
 #new_group a {
-background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -1054px;
 }
 
 .pagination .nav_prev a,
@@ -271,10 +277,10 @@ background-repeat:no-repeat;
 border-color:#C8D1D5;
 }
 .pagination .nav_prev a {
-background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
-background-position:10% 45%;
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:10% -187px;
 }
 .pagination .nav_next a {
-background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
-background-position:90% 45%;
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:105% -252px;
 }