summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZach Copley <zach@status.net>2009-10-13 09:36:26 -0700
committerZach Copley <zach@status.net>2009-10-13 09:36:26 -0700
commitb4b992bca77d34b8643910e8d590b5be7fede94b (patch)
tree3b91cd2913f49fb90b4edfc5c282c9f4d40e7495
parent0190785b73b52e2c6069c31542f578f812a5e0ab (diff)
parent870b091693531ba9aca20a0b0fa64ec326d72725 (diff)
Merge branch '0.9.x' into pluginize-twitter-bridge
* 0.9.x: (247 commits) Added in credits. Use site's name for basic auth realm Make apigroupcreate.php pass phpcs Took out some unnecessary intializations Implemented create group api CamelCase all function names in the API code These same params are used in most API actions; moved to base API class Missed some of the references to the old TwitterApiAction - removed Remove more redundant $formats Remove dead code Move all basic auth output and processing to base classes $format is used by every API action. Set it in the base class. Delete action/api.php and rename lib/twitterapi.php to lib/api.php New actions for blocks via API fix FBConnect so it doesn't muffle EndPrimaryNav don't write session if it's unchanged Fixed facebook connect primary nav to hide search option when site is private and user is not logged in Fixed facebook connect primary nav to obey sms/twitter/openid settings Fixed facebook connect login nav to obey openid settings Fixed facebook connect nav to obey sms/twitter disabled ...
-rw-r--r--EVENTS.txt49
-rw-r--r--README8
-rw-r--r--actions/allrss.php1
-rw-r--r--actions/api.php305
-rw-r--r--actions/apiaccountratelimitstatus.php112
-rw-r--r--actions/apiaccountverifycredentials.php85
-rw-r--r--actions/apiblockcreate.php114
-rw-r--r--actions/apiblockdestroy.php113
-rw-r--r--actions/apidirectmessage.php375
-rw-r--r--actions/apidirectmessagenew.php188
-rw-r--r--actions/apifavoritecreate.php168
-rw-r--r--actions/apifavoritedestroy.php150
-rw-r--r--actions/apifriendshipscreate.php137
-rw-r--r--actions/apifriendshipsdestroy.php139
-rw-r--r--actions/apifriendshipsexists.php128
-rw-r--r--actions/apifriendshipsshow.php168
-rw-r--r--actions/apigroupcreate.php381
-rw-r--r--actions/apigroupismember.php122
-rw-r--r--actions/apigroupjoin.php163
-rw-r--r--actions/apigroupleave.php149
-rw-r--r--actions/apigrouplist.php223
-rw-r--r--actions/apigrouplistall.php208
-rw-r--r--actions/apigroupmembership.php192
-rw-r--r--actions/apigroupshow.php152
-rw-r--r--actions/apihelptest.php96
-rw-r--r--actions/apistatusesdestroy.php154
-rw-r--r--actions/apistatusesshow.php206
-rw-r--r--actions/apistatusesupdate.php241
-rw-r--r--actions/apistatusnetconfig.php142
-rw-r--r--actions/apistatusnetversion.php102
-rw-r--r--actions/apisubscriptions.php266
-rw-r--r--actions/apitimelinefavorites.php237
-rw-r--r--actions/apitimelinefriends.php247
-rw-r--r--actions/apitimelinegroup.php231
-rw-r--r--actions/apitimelinementions.php233
-rw-r--r--actions/apitimelinepublic.php213
-rw-r--r--actions/apitimelinetag.php224
-rw-r--r--actions/apitimelineuser.php248
-rw-r--r--actions/apiuserfollowers.php89
-rw-r--r--actions/apiuserfriends.php89
-rw-r--r--actions/apiusershow.php126
-rw-r--r--actions/avatarsettings.php8
-rw-r--r--actions/deletenotice.php38
-rw-r--r--actions/editgroup.php1
-rw-r--r--actions/favoritesrss.php11
-rw-r--r--actions/finishremotesubscribe.php63
-rw-r--r--actions/foafgroup.php173
-rw-r--r--actions/grouprss.php1
-rw-r--r--actions/groupsearch.php2
-rw-r--r--actions/newnotice.php10
-rw-r--r--actions/publicrss.php18
-rw-r--r--actions/repliesrss.php1
-rw-r--r--actions/showgroup.php7
-rw-r--r--actions/showstream.php7
-rw-r--r--actions/twitapiaccount.php127
-rw-r--r--actions/twitapiblocks.php74
-rw-r--r--actions/twitapidirect_messages.php305
-rw-r--r--actions/twitapifavorites.php216
-rw-r--r--actions/twitapifriendships.php250
-rw-r--r--actions/twitapigroups.php329
-rw-r--r--actions/twitapihelp.php57
-rw-r--r--actions/twitapisearchatom.php8
-rw-r--r--actions/twitapisearchjson.php10
-rw-r--r--actions/twitapistatuses.php606
-rw-r--r--actions/twitapistatusnet.php175
-rw-r--r--actions/twitapitags.php113
-rw-r--r--actions/twitapitrends.php6
-rw-r--r--actions/twitapiusers.php80
-rw-r--r--actions/updateprofile.php49
-rw-r--r--actions/userrss.php5
-rw-r--r--classes/File.php8
-rw-r--r--classes/File_redirection.php3
-rw-r--r--classes/Notice.php54
-rw-r--r--classes/Profile.php75
-rw-r--r--classes/Session.php13
-rw-r--r--classes/User.php87
-rw-r--r--db/sms_carrier.sql3
-rw-r--r--db/statusnet.sql12
-rw-r--r--doc-src/bookmarklet4
-rw-r--r--doc-src/im4
-rw-r--r--extlib/Auth/OpenID/BigMath.php2
-rw-r--r--extlib/Auth/Yadis/XML.php2
-rw-r--r--extlib/OAuth.php2
-rw-r--r--extlib/PEAR.php2
-rw-r--r--index.php36
-rw-r--r--install.php38
-rw-r--r--lib/action.php12
-rw-r--r--lib/api.php (renamed from lib/twitterapi.php)635
-rw-r--r--lib/apiauth.php203
-rw-r--r--lib/apibareauth.php109
-rw-r--r--lib/clienterroraction.php43
-rw-r--r--lib/commandinterpreter.php42
-rw-r--r--lib/common.php218
-rw-r--r--lib/default.php232
-rw-r--r--lib/deleteaction.php74
-rw-r--r--lib/designsettings.php1
-rw-r--r--lib/error.php6
-rw-r--r--lib/facebookaction.php10
-rw-r--r--lib/htmloutputter.php24
-rw-r--r--lib/mail.php16
-rw-r--r--lib/noticeform.php79
-rw-r--r--lib/noticelist.php32
-rw-r--r--lib/oauthstore.php5
-rw-r--r--lib/right.php50
-rw-r--r--lib/router.php393
-rw-r--r--lib/rssaction.php36
-rw-r--r--lib/schema.php680
-rw-r--r--lib/searchaction.php12
-rw-r--r--lib/servererroraction.php19
-rw-r--r--lib/unqueuemanager.php7
-rw-r--r--lib/util.php73
-rw-r--r--plugins/Autocomplete/Autocomplete.js51
-rw-r--r--plugins/Autocomplete/AutocompletePlugin.php16
-rw-r--r--plugins/Autocomplete/autocomplete.php136
-rw-r--r--plugins/Autocomplete/readme.txt2
-rw-r--r--plugins/FBConnect/FBCLoginGroupNav.php20
-rw-r--r--plugins/FBConnect/FBCSettingsNav.php42
-rw-r--r--plugins/FBConnect/FBConnectPlugin.php43
-rw-r--r--plugins/InfiniteScroll/InfiniteScrollPlugin.php2
-rw-r--r--plugins/InfiniteScroll/infinitescroll.js2
-rw-r--r--plugins/InfiniteScroll/jquery.infinitescroll.js22
-rw-r--r--plugins/Meteor/meteorupdater.js5
-rw-r--r--plugins/OpenID/OpenIDPlugin.php15
-rw-r--r--plugins/Orbited/OrbitedPlugin.php154
-rw-r--r--plugins/Orbited/orbitedextra.js2
-rw-r--r--plugins/Orbited/orbitedupdater.js24
-rw-r--r--plugins/PiwikAnalyticsPlugin.php30
-rw-r--r--plugins/PubSubHubBub/PubSubHubBubPlugin.php4
-rw-r--r--plugins/Realtime/RealtimePlugin.php178
-rw-r--r--plugins/Realtime/icon_external.gifbin0 -> 90 bytes
-rw-r--r--plugins/Realtime/realtimeupdate.js153
-rw-r--r--plugins/TwitterBridge/twitter.php4
-rw-r--r--plugins/TwitterBridge/twitterbasicauthclient.php10
-rw-r--r--plugins/recaptcha/README2
-rw-r--r--scripts/checkschema.php30
-rw-r--r--scripts/createsim.php2
-rw-r--r--scripts/deleteuser.php68
-rwxr-xr-xscripts/getvaliddaemons.php34
-rwxr-xr-xscripts/maildaemon.php9
-rwxr-xr-xscripts/pluginqueuehandler.php58
-rw-r--r--scripts/showtable.php (renamed from actions/twitapinotifications.php)35
-rwxr-xr-xscripts/startdaemons.sh2
-rwxr-xr-xscripts/xmppdaemon.php11
-rw-r--r--tests/HashTagDetectionTests.php1
-rw-r--r--tests/URLDetectionTest.php221
-rw-r--r--tests/UserRightsTest.php59
-rw-r--r--theme/base/css/display.css7
-rw-r--r--theme/base/images/icons/icons-01.pngbin0 -> 3260 bytes
-rw-r--r--theme/cloudy/css/display.css25
-rw-r--r--theme/identica/css/display.css48
-rw-r--r--theme/identica/css/ie.css8
-rw-r--r--theme/iphone/bg-body.gifbin136 -> 0 bytes
-rw-r--r--theme/iphone/bg-header.gifbin2469 -> 0 bytes
-rw-r--r--theme/iphone/default-avatar-mini.pngbin646 -> 0 bytes
-rw-r--r--theme/iphone/default-avatar-profile.pngbin2853 -> 0 bytes
-rw-r--r--theme/iphone/default-avatar-stream.pngbin1487 -> 0 bytes
-rw-r--r--theme/iphone/display.css698
-rw-r--r--theme/iphone/display.css.dist686
-rw-r--r--theme/iphone/ie6.css.dist63
-rw-r--r--theme/iphone/ie7.css.dist20
-rw-r--r--theme/iphone/login-bg.gifbin328 -> 0 bytes
-rw-r--r--theme/iphone/logo.pngbin818 -> 0 bytes
162 files changed, 10501 insertions, 5581 deletions
diff --git a/EVENTS.txt b/EVENTS.txt
index 121ae175d..02b11a8a6 100644
--- a/EVENTS.txt
+++ b/EVENTS.txt
@@ -87,6 +87,18 @@ StartShowContentBlock: Showing before the content container
EndShowContentBlock: Showing after the content container
- $action: the current action
+StartShowAside: Showing before the Aside container
+- $action: the current action
+
+EndShowAside: Showing after the Aside container
+- $action: the current action
+
+StartShowNoticeFormData: Showing before the notice form data
+- $action: the current action
+
+EndShowNoticeFormData: Showing after the notice form data
+- $action: the current action
+
StartNoticeSave: before inserting a notice (good place for content filters)
- $notice: notice being saved (no ID or URI)
@@ -170,12 +182,6 @@ StartShowBody: called before showing the <body> element and children
EndShowBody: called after showing the <body> element (and </body>)
- $action: action object being shown
-StartHeadChildren: called before showing the children of <head> element (after <head> tag)
-- $action: action object being shown
-
-EndHeadChildren: called after showing the children of <head> element (before </head>)
-- $action: action object being shown
-
StartPersonalGroupNav: beginning of personal group nav menu
- $action: action object being shown
@@ -200,6 +206,12 @@ StartShowExportData: just before showing the <div> with export data (feeds)
EndShowExportData: just after showing the <div> with export data (feeds)
- $action: action object being shown
+StartShowNoticeItem: just before showing the notice item
+- $action: action object being shown
+
+EndShowNoticeItem: just after showing the notice item
+- $action: action object being shown
+
StartShowPageNotice: just before showing the page notice (instructions or error)
- $action: action object being shown
@@ -254,3 +266,28 @@ StartApiRss: after the rss <channel> element is started
StartApiAtom: after the <feed> element is started
- $action: action object being shown
+StartEnqueueNotice: about to add a notice to the queues (good place to add a new transport)
+- $notice: the notice being added
+- &$transports: modifiable list of transports (as strings) to queue for
+
+EndEnqueueNotice: after adding a notice to the queues
+- $notice: the notice being added
+- $transports: modifiable list of transports to use
+
+UnqueueHandleNotice: Handle a notice when no queue manager is available
+- $notice: the notice to handle
+- $queue: the "queue" that is being executed
+
+GetValidDaemons: Just before determining which daemons to run
+- &$daemons: modifiable list of daemon scripts to run, filenames relative to scripts/
+
+HandleQueuedNotice: Handle a queued notice at queue time (or immediately if no queue)
+- &$notice: notice to handle
+
+StartShowHeadElements: Right after the <head> tag
+- $action: the current action
+
+EndShowHeadElements: Right before the </head> tag; put <script>s here if you need them in <head>
+- $action: the current action
+
+CheckSchema: chance to check the schema
diff --git a/README b/README
index f3b2528b8..486656a3b 100644
--- a/README
+++ b/README
@@ -1037,6 +1037,14 @@ utf8: whether to talk to the database in UTF-8 mode. This is the default
with new installations, but older sites may want to turn it off
until they get their databases fixed up. See "UTF-8 database"
above for details.
+schemacheck: when to let plugins check the database schema to add
+ tables or update them. Values can be 'runtime' (default)
+ or 'script'. 'runtime' can be costly (plugins check the
+ schema on every hit, adding potentially several db
+ queries, some quite long), but not everyone knows how to
+ run a script. If you can, set this to 'script' and run
+ scripts/checkschema.php whenever you install or upgrade a
+ plugin.
syslog
------
diff --git a/actions/allrss.php b/actions/allrss.php
index 57efb73f0..28b1be27d 100644
--- a/actions/allrss.php
+++ b/actions/allrss.php
@@ -68,6 +68,7 @@ class AllrssAction extends Rss10Action
$this->clientError(_('No such user.'));
return false;
} else {
+ $this->notices = $this->getNotices($this->limit);
return true;
}
}
diff --git a/actions/api.php b/actions/api.php
deleted file mode 100644
index d570bb017..000000000
--- a/actions/api.php
+++ /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/>.
- *
- * @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/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
index 000000000..96179f175
--- /dev/null
+++ b/actions/apiaccountratelimitstatus.php
@@ -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
index 000000000..08b201dbf
--- /dev/null
+++ b/actions/apiaccountverifycredentials.php
@@ -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
index 000000000..1cab2df5d
--- /dev/null
+++ b/actions/apiblockcreate.php
@@ -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
index 000000000..16dbf94ca
--- /dev/null
+++ b/actions/apiblockdestroy.php
@@ -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
index 000000000..a21fe86d2
--- /dev/null
+++ b/actions/apidirectmessage.php
@@ -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
index 000000000..fa6cafbe8
--- /dev/null
+++ b/actions/apidirectmessagenew.php
@@ -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
index 000000000..a80a6492e
--- /dev/null
+++ b/actions/apifavoritecreate.php
@@ -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
index 000000000..f131d1c7f
--- /dev/null
+++ b/actions/apifavoritedestroy.php
@@ -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
index 000000000..a824e734b
--- /dev/null
+++ b/actions/apifriendshipscreate.php
@@ -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
index 000000000..3d9b7e001
--- /dev/null
+++ b/actions/apifriendshipsdestroy.php
@@ -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
index 000000000..ae50c512c
--- /dev/null
+++ b/actions/apifriendshipsexists.php
@@ -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
index 000000000..8fc436738
--- /dev/null
+++ b/actions/apifriendshipsshow.php
@@ -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
index 000000000..cdb2afb5b
--- /dev/null
+++ b/actions/apigroupcreate.php
@@ -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
index 000000000..a8a40a6b3
--- /dev/null
+++ b/actions/apigroupismember.php
@@ -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
index 000000000..071cd9290
--- /dev/null
+++ b/actions/apigroupjoin.php
@@ -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
index 000000000..0d4bb9e4d
--- /dev/null
+++ b/actions/apigroupleave.php
@@ -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
index 000000000..c529c1e40
--- /dev/null
+++ b/actions/apigrouplist.php
@@ -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
index 000000000..89469f36f
--- /dev/null
+++ b/actions/apigrouplistall.php
@@ -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
index 000000000..b31e47b39
--- /dev/null
+++ b/actions/apigroupmembership.php
@@ -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
index 000000000..2bdb22bc4
--- /dev/null
+++ b/actions/apigroupshow.php
@@ -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
index 000000000..e4ef55f2e
--- /dev/null
+++ b/actions/apihelptest.php
@@ -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
index 000000000..8dc8793b5
--- /dev/null
+++ b/actions/apistatusesdestroy.php
@@ -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
index 000000000..3be22ca59
--- /dev/null
+++ b/actions/apistatusesshow.php
@@ -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
index 000000000..0d71e1512
--- /dev/null
+++ b/actions/apistatusesupdate.php
@@ -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
index 000000000..ed1d151bf
--- /dev/null
+++ b/actions/apistatusnetconfig.php
@@ -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
index 000000000..e73ab983b
--- /dev/null
+++ b/actions/apistatusnetversion.php
@@ -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
index 000000000..bc68dd192
--- /dev/null
+++ b/actions/apisubscriptions.php
@@ -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
index 000000000..b8ae74f13
--- /dev/null
+++ b/actions/apitimelinefavorites.php
@@ -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
index 000000000..1ea35866e
--- /dev/null
+++ b/actions/apitimelinefriends.php
@@ -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
index 000000000..5d0542918
--- /dev/null
+++ b/actions/apitimelinegroup.php
@@ -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
index 000000000..fe5ff0f28
--- /dev/null
+++ b/actions/apitimelinementions.php
@@ -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
index 000000000..58e267734
--- /dev/null
+++ b/actions/apitimelinepublic.php
@@ -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
index 000000000..a274daac0
--- /dev/null
+++ b/actions/apitimelinetag.php
@@ -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
index 000000000..285735fd1
--- /dev/null
+++ b/actions/apitimelineuser.php
@@ -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
index 000000000..e8d92a773
--- /dev/null
+++ b/actions/apiuserfollowers.php
@@ -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
index 000000000..741a26e58
--- /dev/null
+++ b/actions/apiuserfriends.php
@@ -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
index 000000000..b3a939b43
--- /dev/null
+++ b/actions/apiusershow.php
@@ -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/avatarsettings.php b/actions/avatarsettings.php
index 02a684b38..ded419dd7 100644
--- a/actions/avatarsettings.php
+++ b/actions/avatarsettings.php
@@ -362,13 +362,13 @@ class AvatarsettingsAction extends AccountSettingsAction
$profile = $user->getProfile();
$avatar = $profile->getOriginalAvatar();
- $avatar->delete();
+ if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
- $avatar->delete();
+ if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
- $avatar->delete();
+ if($avatar) $avatar->delete();
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
- $avatar->delete();
+ if($avatar) $avatar->delete();
$this->showForm(_('Avatar deleted.'), true);
}
diff --git a/actions/deletenotice.php b/actions/deletenotice.php
index 3d040f2fa..4a48a9c34 100644
--- a/actions/deletenotice.php
+++ b/actions/deletenotice.php
@@ -32,15 +32,45 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/lib/deleteaction.php';
-
-class DeletenoticeAction extends DeleteAction
+class DeletenoticeAction extends Action
{
- var $error = null;
+ var $error = null;
+ var $user = null;
+ var $notice = null;
+ var $profile = null;
+ var $user_profile = null;
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $this->user = common_current_user();
+ $notice_id = $this->trimmed('notice');
+ $this->notice = Notice::staticGet($notice_id);
+
+ if (!$this->notice) {
+ common_user_error(_('No such notice.'));
+ exit;
+ }
+
+ $this->profile = $this->notice->getProfile();
+ $this->user_profile = $this->user->getProfile();
+
+ return true;
+ }
function handle($args)
{
parent::handle($args);
+
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ exit;
+ } else if ($this->notice->profile_id != $this->user_profile->id &&
+ !$this->user->hasRight(Right::deleteOthersNotice)) {
+ common_user_error(_('Can\'t delete this notice.'));
+ exit;
+ }
// XXX: Ajax!
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
diff --git a/actions/editgroup.php b/actions/editgroup.php
index 0c2dc8bdf..5dd039f8a 100644
--- a/actions/editgroup.php
+++ b/actions/editgroup.php
@@ -250,7 +250,6 @@ class EditgroupAction extends GroupDesignAction
$this->group->homepage = $homepage;
$this->group->description = $description;
$this->group->location = $location;
- $this->group->created = common_sql_now();
$result = $this->group->update($orig);
diff --git a/actions/favoritesrss.php b/actions/favoritesrss.php
index 2d5ce9854..62f06e841 100644
--- a/actions/favoritesrss.php
+++ b/actions/favoritesrss.php
@@ -50,11 +50,11 @@ require_once INSTALLDIR.'/lib/rssaction.php';
*/
class FavoritesrssAction extends Rss10Action
{
-
+
/** The user whose favorites to display */
-
+
var $user = null;
-
+
/**
* Find the user to display by supplied nickname
*
@@ -66,7 +66,7 @@ class FavoritesrssAction extends Rss10Action
function prepare($args)
{
parent::prepare($args);
-
+
$nickname = $this->trimmed('nickname');
$this->user = User::staticGet('nickname', $nickname);
@@ -74,10 +74,11 @@ class FavoritesrssAction extends Rss10Action
$this->clientError(_('No such user.'));
return false;
} else {
+ $this->notices = $this->getNotices($this->limit);
return true;
}
}
-
+
/**
* Get notices
*
diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php
index 5f6807d10..b1cec66f4 100644
--- a/actions/finishremotesubscribe.php
+++ b/actions/finishremotesubscribe.php
@@ -143,67 +143,4 @@ class FinishremotesubscribeAction extends Action
$user->nickname)),
303);
}
-
- function add_avatar($profile, $url)
- {
- $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
- copy($url, $temp_filename);
- $imagefile = new ImageFile($profile->id, $temp_filename);
- $filename = Avatar::filename($profile->id,
- image_type_to_extension($imagefile->type),
- null,
- common_timestamp());
- rename($temp_filename, Avatar::path($filename));
- return $profile->setOriginal($filename);
- }
-
- function access_token($omb)
- {
-
- common_debug('starting request for access token', __FILE__);
-
- $con = omb_oauth_consumer();
- $tok = new OAuthToken($omb['token'], $omb['secret']);
-
- common_debug('using request token "'.$tok.'"', __FILE__);
-
- $url = $omb['access_token_url'];
-
- common_debug('using access token url "'.$url.'"', __FILE__);
-
- # XXX: Is this the right thing to do? Strip off GET params and make them
- # POST params? Seems wrong to me.
-
- $parsed = parse_url($url);
- $params = array();
- parse_str($parsed['query'], $params);
-
- $req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params);
-
- $req->set_parameter('omb_version', OMB_VERSION_01);
-
- # XXX: test to see if endpoint accepts this signature method
-
- $req->sign_request(omb_hmac_sha1(), $con, $tok);
-
- # We re-use this tool's fetcher, since it's pretty good
-
- common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__);
- common_debug('posting request data "'.$req->to_postdata().'"', __FILE__);
-
- $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
- $result = $fetcher->post($req->get_normalized_http_url(),
- $req->to_postdata(),
- array('User-Agent: StatusNet/' . STATUSNET_VERSION));
-
- common_debug('got result: "'.print_r($result,true).'"', __FILE__);
-
- if ($result->status != 200) {
- return null;
- }
-
- parse_str($result->body, $return);
-
- return array($return['oauth_token'], $return['oauth_token_secret']);
- }
}
diff --git a/actions/foafgroup.php b/actions/foafgroup.php
new file mode 100644
index 000000000..f5fd7fe88
--- /dev/null
+++ b/actions/foafgroup.php
@@ -0,0 +1,173 @@
+<?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 Mail
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Toby Inkster <mail@tobyinkster.co.uk>
+ * @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); }
+
+class FoafGroupAction extends Action
+{
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $nickname_arg = $this->arg('nickname');
+
+ if (empty($nickname_arg)) {
+ $this->clientError(_('No such group.'), 404);
+ return false;
+ }
+
+ $this->nickname = common_canonical_nickname($nickname_arg);
+
+ // Permanent redirect on non-canonical nickname
+
+ if ($nickname_arg != $this->nickname) {
+ common_redirect(common_local_url('foafgroup',
+ array('nickname' => $this->nickname)),
+ 301);
+ return false;
+ }
+
+ $this->group = User_group::staticGet('nickname', $this->nickname);
+
+ if (!$this->group) {
+ $this->clientError(_('No such group.'), 404);
+ return false;
+ }
+
+ common_set_returnto($this->selfUrl());
+
+ return true;
+ }
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ header('Content-Type: application/rdf+xml');
+
+ $this->startXML();
+ $this->elementStart('rdf:RDF', array('xmlns:rdf' =>
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'xmlns:dcterms' =>
+ 'http://purl.org/dc/terms/',
+ 'xmlns:sioc' =>
+ 'http://rdfs.org/sioc/ns#',
+ 'xmlns:foaf' =>
+ 'http://xmlns.com/foaf/0.1/',
+ 'xmlns:statusnet' =>
+ 'http://status.net/ont/',
+ 'xmlns' => 'http://xmlns.com/foaf/0.1/'));
+
+ $this->showPpd(common_local_url('foafgroup', array('nickname' => $this->nickname)), $this->group->permalink());
+
+ $this->elementStart('Group', array('rdf:about' =>
+ $this->group->permalink()));
+ if ($this->group->fullname) {
+ $this->element('name', null, $this->group->fullname);
+ }
+ if ($this->group->description) {
+ $this->element('dcterms:description', null, $this->group->description);
+ }
+ if ($this->group->nickname) {
+ $this->element('dcterms:identifier', null, $this->group->nickname);
+ $this->element('nick', null, $this->group->nickname);
+ }
+ foreach ($this->group->getAliases() as $alias) {
+ $this->element('nick', null, $alias);
+ }
+ if ($this->group->homeUrl()) {
+ $this->element('weblog', array('rdf:resource' => $this->group->homeUrl()));
+ }
+ if ($this->group->homepage) {
+ $this->element('page', array('rdf:resource' => $this->group->homepage));
+ }
+ if ($this->group->homepage_logo) {
+ $this->element('depiction', array('rdf:resource' => $this->group->homepage_logo));
+ }
+
+ $members = $this->group->getMembers();
+ $member_details = array();
+ while ($members->fetch()) {
+ $member_uri = common_local_url('userbyid', array('id'=>$members->id));
+ $member_details[$member_uri] = array(
+ 'nickname' => $members->nickname
+ );
+ $this->element('member', array('rdf:resource' => $member_uri));
+ }
+
+ $admins = $this->group->getAdmins();
+ while ($admins->fetch()) {
+ $admin_uri = common_local_url('userbyid', array('id'=>$admins->id));
+ $member_details[$admin_uri]['is_admin'] = true;
+ $this->element('statusnet:groupAdmin', array('rdf:resource' => $admin_uri));
+ }
+
+ $this->elementEnd('Group');
+
+ ksort($member_details);
+ foreach ($member_details as $uri => $details) {
+ if ($details['is_admin'])
+ {
+ $this->elementStart('Agent', array('rdf:about' => $uri));
+ $this->element('nick', null, $details['nickname']);
+ $this->elementStart('holdsAccount');
+ $this->elementStart('sioc:User', array('rdf:about'=>$uri.'#acct'));
+ $this->elementStart('sioc:has_function');
+ $this->elementStart('statusnet:GroupAdminRole');
+ $this->element('sioc:scope', array('rdf:resource' => $this->group->permalink()));
+ $this->elementEnd('statusnet:GroupAdminRole');
+ $this->elementEnd('sioc:has_function');
+ $this->elementEnd('sioc:User');
+ $this->elementEnd('holdsAccount');
+ $this->elementEnd('Agent');
+ }
+ else
+ {
+ $this->element('Agent', array(
+ 'foaf:nick' => $details['nickname'],
+ 'rdf:about' => $uri,
+ ));
+ }
+ }
+
+ $this->elementEnd('rdf:RDF');
+ $this->endXML();
+ }
+
+ function showPpd($foaf_url, $person_uri)
+ {
+ $this->elementStart('Document', array('rdf:about' => $foaf_url));
+ $this->element('primaryTopic', array('rdf:resource' => $person_uri));
+ $this->elementEnd('Document');
+ }
+
+} \ No newline at end of file
diff --git a/actions/grouprss.php b/actions/grouprss.php
index 70c1ded48..6a6b55e78 100644
--- a/actions/grouprss.php
+++ b/actions/grouprss.php
@@ -104,6 +104,7 @@ class groupRssAction extends Rss10Action
return false;
}
+ $this->notices = $this->getNotices($this->limit);
return true;
}
diff --git a/actions/groupsearch.php b/actions/groupsearch.php
index f0cca7156..55f4cee62 100644
--- a/actions/groupsearch.php
+++ b/actions/groupsearch.php
@@ -105,7 +105,7 @@ class GroupSearchResults extends GroupList
function __construct($user_group, $terms, $action)
{
- parent::__construct($user_group, $terms, $action);
+ parent::__construct($user_group, null, $action);
$this->terms = array_map('preg_quote',
array_map('htmlspecialchars', $terms));
$this->pattern = '/('.implode('|',$terms).')/i';
diff --git a/actions/newnotice.php b/actions/newnotice.php
index 4c6372c2b..d5b0332f4 100644
--- a/actions/newnotice.php
+++ b/actions/newnotice.php
@@ -255,13 +255,6 @@ class NewnoticeAction extends Action
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
($replyto == 'false') ? null : $replyto);
- if (is_string($notice)) {
- if (isset($filename)) {
- $this->deleteFile($filename);
- }
- $this->clientError($notice);
- }
-
if (isset($mimetype)) {
$this->attachFile($notice, $fileRecord);
}
@@ -433,13 +426,14 @@ class NewnoticeAction extends Action
$content = $this->trimmed('status_textarea');
if (!$content) {
$replyto = $this->trimmed('replyto');
+ $inreplyto = $this->trimmed('inreplyto');
$profile = Profile::staticGet('nickname', $replyto);
if ($profile) {
$content = '@' . $profile->nickname . ' ';
}
}
- $notice_form = new NoticeForm($this, '', $content);
+ $notice_form = new NoticeForm($this, '', $content, null, $inreplyto);
$notice_form->show();
}
diff --git a/actions/publicrss.php b/actions/publicrss.php
index 593888b9f..0c5d061cb 100644
--- a/actions/publicrss.php
+++ b/actions/publicrss.php
@@ -50,8 +50,22 @@ require_once INSTALLDIR.'/lib/rssaction.php';
class PublicrssAction extends Rss10Action
{
/**
+ * Read arguments and initialize members
+ *
+ * @param array $args Arguments from $_REQUEST
+ * @return boolean success
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+ $this->notices = $this->getNotices($this->limit);
+ return true;
+ }
+
+ /**
* Initialization.
- *
+ *
* @return boolean true
*/
function init()
@@ -73,7 +87,7 @@ class PublicrssAction extends Rss10Action
while ($notice->fetch()) {
$notices[] = clone($notice);
}
-
+
return $notices;
}
diff --git a/actions/repliesrss.php b/actions/repliesrss.php
index c71c9226f..76aae21ad 100644
--- a/actions/repliesrss.php
+++ b/actions/repliesrss.php
@@ -38,6 +38,7 @@ class RepliesrssAction extends Rss10Action
$this->clientError(_('No such user.'));
return false;
} else {
+ $this->notices = $this->getNotices($this->limit);
return true;
}
}
diff --git a/actions/showgroup.php b/actions/showgroup.php
index ff9949762..a67765ce5 100644
--- a/actions/showgroup.php
+++ b/actions/showgroup.php
@@ -345,7 +345,12 @@ class ShowgroupAction extends GroupDesignAction
'method' => 'timeline',
'argument' => $this->group->nickname.'.atom')),
sprintf(_('Notice feed for %s group (Atom)'),
- $this->group->nickname)));
+ $this->group->nickname)),
+ new Feed(Feed::FOAF,
+ common_local_url('foafgroup',
+ array('nickname' => $this->group->nickname)),
+ sprintf(_('FOAF for %s group'),
+ $this->group->nickname)));
}
/**
diff --git a/actions/showstream.php b/actions/showstream.php
index 2e9679fae..cdac4f47b 100644
--- a/actions/showstream.php
+++ b/actions/showstream.php
@@ -378,8 +378,13 @@ class ShowstreamAction extends ProfileAction
$this->showEmptyListMessage();
}
+ $args = array('nickname' => $this->user->nickname);
+ if (!empty($this->tag))
+ {
+ $args['tag'] = $this->tag;
+ }
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
- 'showstream', array('nickname' => $this->user->nickname));
+ 'showstream', $args);
}
function showAnonymousMessage()
diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php
deleted file mode 100644
index 93c8443c9..000000000
--- a/actions/twitapiaccount.php
+++ /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
index ed17946ae..000000000
--- a/actions/twitapiblocks.php
+++ /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
index 08b8f4e9c..000000000
--- a/actions/twitapidirect_messages.php
+++ /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
index f8943fe2d..000000000
--- a/actions/twitapifavorites.php
+++ /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
index eea8945c3..000000000
--- a/actions/twitapifriendships.php
+++ /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
index 4deb1b764..000000000
--- a/actions/twitapigroups.php
+++ /dev/null
@@ -1,329 +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 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);
- }
- }
-}
diff --git a/actions/twitapihelp.php b/actions/twitapihelp.php
deleted file mode 100644
index 81381620e..000000000
--- a/actions/twitapihelp.php
+++ /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/twitapisearchatom.php b/actions/twitapisearchatom.php
index 2f587d604..7d618c471 100644
--- a/actions/twitapisearchatom.php
+++ b/actions/twitapisearchatom.php
@@ -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');
diff --git a/actions/twitapisearchjson.php b/actions/twitapisearchjson.php
index c628ee624..c7fa741a0 100644
--- a/actions/twitapisearchjson.php
+++ b/actions/twitapisearchjson.php
@@ -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
index ad6654dff..000000000
--- a/actions/twitapistatuses.php
+++ /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 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);
-
- if (is_string($notice)) {
- $this->serverError($notice);
- return;
- }
-
- 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=! (boolean) $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=! (boolean) $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
index 490f11dce..000000000
--- a/actions/twitapistatusnet.php
+++ /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
index 0bcc55d37..000000000
--- a/actions/twitapitags.php
+++ /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);
- }
- }
-
-}
diff --git a/actions/twitapitrends.php b/actions/twitapitrends.php
index 83ab28f35..779405e6d 100644
--- a/actions/twitapitrends.php
+++ b/actions/twitapitrends.php
@@ -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
index 703fa6754..000000000
--- a/actions/twitapiusers.php
+++ /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/actions/updateprofile.php b/actions/updateprofile.php
index d9cc7f7f3..3cec9523c 100644
--- a/actions/updateprofile.php
+++ b/actions/updateprofile.php
@@ -55,46 +55,13 @@ class UpdateprofileAction extends Action
*/
function prepare($argarray)
{
- $version = $req->get_parameter('omb_version');
- if ($version != OMB_VERSION_01) {
- $this->clientError(_('Unsupported OMB version'), 400);
- return false;
- }
- # First, check to see if listenee exists
- $listenee = $req->get_parameter('omb_listenee');
- $remote = Remote_profile::staticGet('uri', $listenee);
- if (!$remote) {
- $this->clientError(_('Profile unknown'), 404);
- return false;
- }
- # Second, check to see if they should be able to post updates!
- # We see if there are any subscriptions to that remote user with
- # the given token.
-
- $sub = new Subscription();
- $sub->subscribed = $remote->id;
- $sub->token = $token->key;
- if (!$sub->find(true)) {
- $this->clientError(_('You did not send us that profile'), 403);
- return false;
- }
-
- $profile = Profile::staticGet('id', $remote->id);
- if (!$profile) {
- # This one is our fault
- $this->serverError(_('Remote profile with no matching profile'), 500);
- return false;
- }
- $nickname = $req->get_parameter('omb_listenee_nickname');
- if ($nickname && !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.'));
- return false;
- }
- $license = $req->get_parameter('omb_listenee_license');
- if ($license && !common_valid_http_url($license)) {
- $this->clientError(sprintf(_("Invalid license URL '%s'"), $license));
+ parent::prepare($argarray);
+ $license = $_POST['omb_listenee_license'];
+ $site_license = common_config('license', 'url');
+ if (!common_compatible_license($license, $site_license)) {
+ $this->clientError(sprintf(_('Listenee stream license ‘%s’ is not '.
+ 'compatible with site license ‘%s’.'),
+ $license, $site_license));
return false;
}
return true;
@@ -113,4 +80,4 @@ class UpdateprofileAction extends Action
return;
}
}
-} \ No newline at end of file
+}
diff --git a/actions/userrss.php b/actions/userrss.php
index fa6d588cd..19e610551 100644
--- a/actions/userrss.php
+++ b/actions/userrss.php
@@ -25,7 +25,6 @@ require_once(INSTALLDIR.'/lib/rssaction.php');
class UserrssAction extends Rss10Action
{
- var $user = null;
var $tag = null;
function prepare($args)
@@ -39,6 +38,7 @@ class UserrssAction extends Rss10Action
$this->clientError(_('No such user.'));
return false;
} else {
+ $this->notices = $this->getNotices($this->limit);
return true;
}
}
@@ -64,9 +64,8 @@ class UserrssAction extends Rss10Action
function getNotices($limit=0)
{
-
$user = $this->user;
-
+
if (is_null($user)) {
return null;
}
diff --git a/classes/File.php b/classes/File.php
index 9758cf7f5..e04a9d525 100644
--- a/classes/File.php
+++ b/classes/File.php
@@ -94,7 +94,13 @@ class File extends Memcached_DataObject
$file_redir = File_redirection::staticGet('url', $given_url);
if (empty($file_redir)) {
$redir_data = File_redirection::where($given_url);
- $redir_url = $redir_data['url'];
+ if (is_array($redir_data)) {
+ $redir_url = $redir_data['url'];
+ } elseif (is_string($redir_data)) {
+ $redir_url = $redir_data;
+ } else {
+ throw new ServerException("Can't process url '$given_url'");
+ }
// TODO: max field length
if ($redir_url === $given_url || strlen($redir_url) > 255) {
$x = File::saveNew($redir_data, $given_url);
diff --git a/classes/File_redirection.php b/classes/File_redirection.php
index 76b18f672..79052bf7d 100644
--- a/classes/File_redirection.php
+++ b/classes/File_redirection.php
@@ -79,6 +79,9 @@ class File_redirection extends Memcached_DataObject
}
}
+ if(strpos($short_url,'://') === false){
+ return $short_url;
+ }
$curlh = File_redirection::_commonCurl($short_url, $redirs);
// Don't include body in output
curl_setopt($curlh, CURLOPT_NOBODY, true);
diff --git a/classes/Notice.php b/classes/Notice.php
index 2138e0561..ba2227c0a 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
@@ -15,9 +15,26 @@
*
* 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 Notices
+ * @package StatusNet
+ * @author Brenda Wallace <shiny@cpan.org>
+ * @author Christopher Vollick <psycotica0@gmail.com>
+ * @author CiaranG <ciaran@ciarang.com>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Evan Prodromou <evan@controlezvous.ca>
+ * @author Gina Haeussge <osd@foosel.net>
+ * @author Jeffery To <jeffery.to@gmail.com>
+ * @author Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author Robin Millette <millette@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @author Tom Adams <tom@holizz.com>
+ * @license GNU Affero General Public License http://www.gnu.org/licenses/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
/**
* Table Definition for notice
@@ -153,30 +170,30 @@ class Notice extends Memcached_DataObject
$final = common_shorten_links($content);
if (Notice::contentTooLong($final)) {
- common_log(LOG_INFO, 'Rejecting notice that is too long.');
- return _('Problem saving notice. Too long.');
+ throw new ClientException(_('Problem saving notice. Too long.'));
}
if (!$profile) {
- common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
- return _('Problem saving notice. Unknown user.');
+ throw new ClientException(_('Problem saving notice. Unknown user.'));
}
if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) {
common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.');
- return _('Too many notices too fast; take a breather and post again in a few minutes.');
+ throw new ClientException(_('Too many notices too fast; take a breather '.
+ 'and post again in a few minutes.'));
}
if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
- return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
+ throw new ClientException(_('Too many duplicate messages too quickly;'.
+ ' take a breather and post again in a few minutes.'));
}
- $banned = common_config('profile', 'banned');
+ $banned = common_config('profile', 'banned');
if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) {
common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id).");
- return _('You are banned from posting notices on this site.');
+ throw new ClientException(_('You are banned from posting notices on this site.'));
}
$notice = new Notice();
@@ -200,12 +217,12 @@ class Notice extends Memcached_DataObject
$notice->created = common_sql_now();
}
- $notice->content = $final;
- $notice->rendered = common_render_content($final, $notice);
- $notice->source = $source;
- $notice->uri = $uri;
+ $notice->content = $final;
+ $notice->rendered = common_render_content($final, $notice);
+ $notice->source = $source;
+ $notice->uri = $uri;
- $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
+ $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
if (!empty($notice->reply_to)) {
$reply = Notice::staticGet('id', $notice->reply_to);
@@ -222,7 +239,7 @@ class Notice extends Memcached_DataObject
if (!$id) {
common_log_db_error($notice, 'INSERT', __FILE__);
- return _('Problem saving notice.');
+ throw new ServerException(_('Problem saving notice.'));
}
// Update ID-dependent columns: URI, conversation
@@ -247,7 +264,7 @@ class Notice extends Memcached_DataObject
if ($changed) {
if (!$notice->update($orig)) {
common_log_db_error($notice, 'UPDATE', __FILE__);
- return _('Problem saving notice.');
+ throw new ServerException(_('Problem saving notice.'));
}
}
@@ -909,7 +926,8 @@ class Notice extends Memcached_DataObject
$qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') ";
$cnt++;
if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
- Notice_inbox::gc($id);
+ // FIXME: Causes lag in replicated servers
+ // Notice_inbox::gc($id);
}
if ($cnt >= MAX_BOXCARS) {
$inbox = new Notice_inbox();
diff --git a/classes/Profile.php b/classes/Profile.php
index 7f0d12758..4a069ee84 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -476,4 +476,79 @@ class Profile extends Memcached_DataObject
$biolimit = self::maxBio();
return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
}
+
+ function delete()
+ {
+ $this->_deleteNotices();
+ $this->_deleteSubscriptions();
+ $this->_deleteMessages();
+ $this->_deleteTags();
+ $this->_deleteBlocks();
+
+ $related = array('Avatar',
+ 'Reply',
+ 'Group_member',
+ );
+
+ foreach ($related as $cls) {
+ $inst = new $cls();
+ $inst->profile_id = $this->id;
+ $inst->delete();
+ }
+
+ parent::delete();
+ }
+
+ function _deleteNotices()
+ {
+ $notice = new Notice();
+ $notice->profile_id = $this->id;
+
+ if ($notice->find()) {
+ while ($notice->fetch()) {
+ $other = clone($notice);
+ $other->delete();
+ }
+ }
+ }
+
+ function _deleteSubscriptions()
+ {
+ $sub = new Subscription();
+ $sub->subscriber = $this->id;
+ $sub->delete();
+
+ $subd = new Subscription();
+ $subd->subscribed = $this->id;
+ $subd->delete();
+ }
+
+ function _deleteMessages()
+ {
+ $msg = new Message();
+ $msg->from_profile = $this->id;
+ $msg->delete();
+
+ $msg = new Message();
+ $msg->to_profile = $this->id;
+ $msg->delete();
+ }
+
+ function _deleteTags()
+ {
+ $tag = new Profile_tag();
+ $tag->tagged = $this->id;
+ $tag->delete();
+ }
+
+ function _deleteBlocks()
+ {
+ $block = new Profile_block();
+ $block->blocked = $this->id;
+ $block->delete();
+
+ $block = new Group_block();
+ $block->blocked = $this->id;
+ $block->delete();
+ }
}
diff --git a/classes/Session.php b/classes/Session.php
index d641edbbe..79a69a96e 100644
--- a/classes/Session.php
+++ b/classes/Session.php
@@ -85,9 +85,18 @@ class Session extends Memcached_DataObject
return $session->insert();
} else {
- $session->session_data = $session_data;
+ if (strcmp($session->session_data, $session_data) == 0) {
+ self::logdeb("Not writing session '$id'; unchanged");
+ return true;
+ } else {
+ self::logdeb("Session '$id' data changed; updating");
+
+ $orig = clone($session);
+
+ $session->session_data = $session_data;
- return $session->update();
+ return $session->update($orig);
+ }
}
}
diff --git a/classes/User.php b/classes/User.php
index 11cb4f08b..48df0cdd7 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -117,11 +117,15 @@ class User extends Memcached_DataObject
function allowed_nickname($nickname)
{
// XXX: should already be validated for size, content, etc.
- static $blacklist = array('rss', 'xrds', 'doc', 'main',
- 'settings', 'notice', 'user',
- 'search', 'avatar', 'tag', 'tags',
- 'api', 'message', 'group', 'groups',
- 'local');
+
+ $blacklist = array();
+
+ //all directory and file names should be blacklisted
+ $d = dir(INSTALLDIR);
+ while (false !== ($entry = $d->read())) {
+ $blacklist[]=$entry;
+ }
+ $d->close();
$merged = array_merge($blacklist, common_config('nickname', 'blacklist'));
return !in_array($nickname, $merged);
}
@@ -707,4 +711,77 @@ class User extends Memcached_DataObject
return true;
}
+
+ /**
+ * Does this user have the right to do X?
+ *
+ * With our role-based authorization, this is merely a lookup for whether the user
+ * has a particular role. The implementation currently uses a switch statement
+ * to determine if the user has the pre-defined role to exercise the right. Future
+ * implementations may allow per-site roles, and different mappings of roles to rights.
+ *
+ * @param $right string Name of the right, usually a constant in class Right
+ * @return boolean whether the user has the right in question
+ */
+
+ function hasRight($right)
+ {
+ $result = false;
+ if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
+ switch ($right)
+ {
+ case Right::deleteOthersNotice:
+ $result = $this->hasRole('moderator');
+ break;
+ default:
+ $result = false;
+ break;
+ }
+ }
+ return $result;
+ }
+
+ function delete()
+ {
+ $profile = $this->getProfile();
+ $profile->delete();
+
+ $related = array('Fave',
+ 'User_openid',
+ 'Confirm_address',
+ 'Remember_me',
+ 'Foreign_link',
+ 'Invitation',
+ );
+
+ if (common_config('inboxes', 'enabled')) {
+ $related[] = 'Notice_inbox';
+ }
+
+ foreach ($related as $cls) {
+ $inst = new $cls();
+ $inst->user_id = $this->id;
+ $inst->delete();
+ }
+
+ $this->_deleteTags();
+ $this->_deleteBlocks();
+
+ parent::delete();
+ }
+
+ function _deleteTags()
+ {
+ $tag = new Profile_tag();
+ $tag->tagger = $this->id;
+ $tag->delete();
+ }
+
+ function _deleteBlocks()
+ {
+ $block = new Profile_block();
+ $block->blocker = $this->id;
+ $block->delete();
+ // XXX delete group block? Reset blocker?
+ }
}
diff --git a/db/sms_carrier.sql b/db/sms_carrier.sql
index 055606f58..0e94df296 100644
--- a/db/sms_carrier.sql
+++ b/db/sms_carrier.sql
@@ -61,4 +61,5 @@ VALUES
(100113, 'T-Mobile Germany', '%s@t-mobile-sms.de', now()),
(100114, 'Vodafone Germany', '%s@vodafone-sms.de', now()),
(100115, 'E-Plus', '%s@smsmail.eplus.de', now()),
- (100116, 'Cellular South', '%s@csouth1.com', now());
+ (100116, 'Cellular South', '%s@csouth1.com', now()),
+ (100117, 'ChinaMobile (139)', '%s@139.com', now());
diff --git a/db/statusnet.sql b/db/statusnet.sql
index 221d60ce3..dfcddb643 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -195,18 +195,6 @@ create table nonce (
constraint primary key (consumer_key, ts, nonce)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
-/* One-to-many relationship of user to openid_url */
-
-create table user_openid (
- canonical varchar(255) primary key comment 'Canonical true URL',
- display varchar(255) not null unique key comment 'URL for viewing, may be different from canonical',
- user_id integer not null comment 'user owning this URL' references user (id),
- created datetime not null comment 'date this record was created',
- modified timestamp comment 'date this record was modified',
-
- index user_openid_user_id_idx (user_id)
-) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
-
/* These are used by JanRain OpenID library */
create table oid_associations (
diff --git a/doc-src/bookmarklet b/doc-src/bookmarklet
index 6cd2c08f9..e5ded7702 100644
--- a/doc-src/bookmarklet
+++ b/doc-src/bookmarklet
@@ -2,6 +2,4 @@ A bookmarklet is a small piece of javascript code used as a bookmark. This one w
Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy.
-<MTMarkdownOptions output='raw'>
-<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a>
-</MTMarkdownOptions>
+<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&amp;status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a>
diff --git a/doc-src/im b/doc-src/im
index c722a4e2c..631f6d9bb 100644
--- a/doc-src/im
+++ b/doc-src/im
@@ -37,10 +37,10 @@ currently-implemented commands:
* **help**: Show this help. List available Jabber/XMPP commands
* **follow &lt;nickname&gt;**: Subscribe to &lt;nickname&gt;
* **sub &lt;nickname&gt;**: Same as follow
-* **leave &lt;nickname&gt;**: Subscribe to &lt;nickname&gt;
+* **leave &lt;nickname&gt;**: Unsubscribe from &lt;nickname&gt;
* **unsub &lt;nickname&gt;**: Same as leave
* **d &lt;nickname&gt; &lt;text&gt;**: Send direct message to &lt;nickname&gt; with message body &lt;text&gt;
* **get &lt;nickname&gt;**: Get last notice from &lt;nickname&gt;
* **last &lt;nickname&gt;**: Same as 'get'
* **whois &lt;nickname&gt;**: Get Profile info on &lt;nickname&gt;
-* **fav &lt;nickname&gt;**: Add user's last notice as a favorite \ No newline at end of file
+* **fav &lt;nickname&gt;**: Add user's last notice as a favorite
diff --git a/extlib/Auth/OpenID/BigMath.php b/extlib/Auth/OpenID/BigMath.php
index 45104947d..b5fc627a0 100644
--- a/extlib/Auth/OpenID/BigMath.php
+++ b/extlib/Auth/OpenID/BigMath.php
@@ -376,7 +376,7 @@ function Auth_OpenID_detectMathLibrary($exts)
// Try to load dynamic modules.
if (!$loaded) {
foreach ($extension['modules'] as $module) {
- if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
+ if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($module . "." . PHP_SHLIB_SUFFIX)) {
$loaded = true;
break;
}
diff --git a/extlib/Auth/Yadis/XML.php b/extlib/Auth/Yadis/XML.php
index 4854f12bb..7232d6cbd 100644
--- a/extlib/Auth/Yadis/XML.php
+++ b/extlib/Auth/Yadis/XML.php
@@ -349,7 +349,7 @@ function &Auth_Yadis_getXMLParser()
foreach ($extensions as $name => $params) {
if (!extension_loaded($name)) {
foreach ($params['libname'] as $libname) {
- if (@dl($libname)) {
+ if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($libname)) {
$classname = $params['classname'];
}
}
diff --git a/extlib/OAuth.php b/extlib/OAuth.php
index fd4853554..648627b57 100644
--- a/extlib/OAuth.php
+++ b/extlib/OAuth.php
@@ -327,7 +327,7 @@ class OAuthRequest {/*{{{*/
public function get_normalized_http_url() {/*{{{*/
$parts = parse_url($this->http_url);
- $port = @$parts['port'];
+ $port = isset($parts['port']) ? $parts['port'] : null;
$scheme = $parts['scheme'];
$host = $parts['host'];
$path = @$parts['path'];
diff --git a/extlib/PEAR.php b/extlib/PEAR.php
index 4c24c6006..fcefa964a 100644
--- a/extlib/PEAR.php
+++ b/extlib/PEAR.php
@@ -746,7 +746,7 @@ class PEAR
{
if (!extension_loaded($ext)) {
// if either returns true dl() will produce a FATAL error, stop that
- if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
+ if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1) || !function_exists('dl')) {
return false;
}
if (OS_WINDOWS) {
diff --git a/index.php b/index.php
index d5174c2b7..644812bd5 100644
--- a/index.php
+++ b/index.php
@@ -18,18 +18,19 @@
*
* @category StatusNet
* @package StatusNet
+ * @author Brenda Wallace <shiny@cpan.org>
+ * @author Christopher Vollick <psycotica0@gmail.com>
+ * @author CiaranG <ciaran@ciarang.com>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Evan Prodromou <evan@controlezvous.ca>
+ * @author Gina Haeussge <osd@foosel.net>
+ * @author Jeffery To <jeffery.to@gmail.com>
+ * @author Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author Robin Millette <millette@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @author Tom Adams <tom@holizz.com>
+ *
* @license GNU Affero General Public License http://www.gnu.org/licenses/
- * @author Brenda Wallace <shiny@cpan.org>
- * @author Christopher Vollick <psycotica0@gmail.com>
- * @author CiaranG <ciaran@ciarang.com>
- * @author Craig Andrews <candrews@integralblue.com>
- * @author Evan Prodromou <evan@status.net>
- * @author Gina Haeussge <osd@foosel.net>
- * @author Jeffery To <jeffery.to@gmail.com>
- * @author Mike Cochrane <mikec@mikenz.geek.nz>
- * @author Robin Millette <millette@controlyourself.ca>
- * @author Sarven Capadisli <csarven@status.net>
- * @author Tom Adams <tom@holizz.com>
*/
define('INSTALLDIR', dirname(__FILE__));
@@ -48,12 +49,23 @@ function getPath($req)
) {
return $req['p'];
} else if (array_key_exists('PATH_INFO', $_SERVER)) {
- return $_SERVER['PATH_INFO'];
+ $path = $_SERVER['PATH_INFO'];
+ $script = $_SERVER['SCRIPT_NAME'];
+ if (substr($path, 0, mb_strlen($script)) == $script) {
+ return substr($path, mb_strlen($script));
+ } else {
+ return $path;
+ }
} else {
return null;
}
}
+/**
+ * logs and then displays error messages
+ *
+ * @return void
+ */
function handleError($error)
{
if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
diff --git a/install.php b/install.php
index 81241315e..69f025a6a 100644
--- a/install.php
+++ b/install.php
@@ -1,3 +1,4 @@
+
<?php
/**
* StatusNet - the distributed open-source microblogging tool
@@ -31,6 +32,8 @@
* @author Sarven Capadisli <csarven@status.net>
* @author Tom Adams <tom@holizz.com>
* @license GNU Affero General Public License http://www.gnu.org/licenses/
+ * @version 0.9.x
+ * @link http://status.net
*/
define('INSTALLDIR', dirname(__FILE__));
@@ -242,7 +245,7 @@ function main()
*/
function haveExternalLibrary($external_library)
{
- if (isset($external_library['include']) && ! @include_once $external_library['include'] ) {
+ if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
return false;
}
if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
@@ -254,6 +257,15 @@ function haveExternalLibrary($external_library)
return true;
}
+// Attempt to include a PHP file and report if it worked, while
+// suppressing the annoying warning messages on failure.
+function haveIncludeFile($filename) {
+ $old = error_reporting(error_reporting() & ~E_WARNING);
+ $ok = include_once($filename);
+ error_reporting($old);
+ return $ok;
+}
+
/**
* Check if all is ready for installation
*
@@ -326,12 +338,19 @@ function checkPrereqs()
*/
function checkExtension($name)
{
- if (!extension_loaded($name)) {
- if (!@dl($name.'.so')) {
- return false;
+ if (extension_loaded($name)) {
+ return true;
+ } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
+ // dl will throw a fatal error if it's disabled or we're in safe mode.
+ // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
+ $soname = $name . '.' . PHP_SHLIB_SUFFIX;
+ if (PHP_SHLIB_SUFFIX == 'dll') {
+ $soname = "php_" . $soname;
}
+ return @dl($soname);
+ } else {
+ return false;
}
- return true;
}
/**
@@ -388,7 +407,7 @@ E_O_T;
E_O_T;
foreach ($present_libraries as $library) {
echo '<li>';
- if ($library['url']) {
+ if (isset($library['url'])) {
echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>';
} else {
echo htmlentities($library['name']);
@@ -476,12 +495,7 @@ E_O_T;
function updateStatus($status, $error=false)
{
- echo '<li';
-
- if ($error) {
- echo ' class="error"';
- }
- echo ">$status</li>";
+ echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
}
function handlePost()
diff --git a/lib/action.php b/lib/action.php
index 8056cb9ec..1b2f73752 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -120,16 +120,15 @@ class Action extends HTMLOutputter // lawsuit
{
// XXX: attributes (profile?)
$this->elementStart('head');
- if (Event::handle('StartHeadChildren', array($this))) {
+ if (Event::handle('StartShowHeadElements', array($this))) {
$this->showTitle();
$this->showShortcutIcon();
$this->showStylesheets();
- $this->showScripts();
$this->showOpenSearch();
$this->showFeeds();
$this->showDescription();
$this->extraHead();
- Event::handle('EndHeadChildren', array($this));
+ Event::handle('EndShowHeadElements', array($this));
}
$this->elementEnd('head');
}
@@ -355,6 +354,7 @@ class Action extends HTMLOutputter // lawsuit
Event::handle('EndShowFooter', array($this));
}
$this->elementEnd('div');
+ $this->showScripts();
$this->elementEnd('body');
}
@@ -528,7 +528,10 @@ class Action extends HTMLOutputter // lawsuit
$this->showContentBlock();
Event::handle('EndShowContentBlock', array($this));
}
- $this->showAside();
+ if (Event::handle('StartShowAside', array($this))) {
+ $this->showAside();
+ Event::handle('EndShowAside', array($this));
+ }
$this->elementEnd('div');
}
@@ -879,6 +882,7 @@ class Action extends HTMLOutputter // lawsuit
*/
function handle($argarray=null)
{
+ header('Vary: Accept-Encoding,Cookie');
$lm = $this->lastModified();
$etag = $this->etag();
if ($etag) {
diff --git a/lib/twitterapi.php b/lib/api.php
index d199e4dee..7a63a4a78 100644
--- a/lib/twitterapi.php
+++ b/lib/api.php
@@ -1,9 +1,12 @@
<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, StatusNet, Inc.
+/**
+ * StatusNet, the distributed open-source microblogging tool
*
- * This program is free software: you can redistribute it and/or modify
+ * 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.
@@ -15,17 +18,49 @@
*
* 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') && !defined('LACONICA')) {
+if (!defined('STATUSNET')) {
exit(1);
}
-class TwitterapiAction extends Action
-{
-
- var $auth_user;
+/**
+ * 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.
*
@@ -37,6 +72,14 @@ class TwitterapiAction extends Action
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;
}
@@ -73,7 +116,7 @@ class TwitterapiAction extends Action
return parent::element($tag, $attrs, $content);
}
- function twitter_user_array($profile, $get_notice=false)
+ function twitterUserArray($profile, $get_notice=false)
{
$twitter_user = array();
@@ -100,7 +143,7 @@ class TwitterapiAction extends Action
$twitter_user['friends_count'] = $profile->subscriptionCount();
- $twitter_user['created_at'] = $this->date_twitter($profile->created);
+ $twitter_user['created_at'] = $this->dateTwitter($profile->created);
$twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
@@ -146,24 +189,24 @@ class TwitterapiAction extends Action
$notice = $profile->getCurrentNotice();
if ($notice) {
# don't get user!
- $twitter_user['status'] = $this->twitter_status_array($notice, false);
+ $twitter_user['status'] = $this->twitterStatusArray($notice, false);
}
}
return $twitter_user;
}
- function twitter_status_array($notice, $include_user=true)
+ 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->date_twitter($notice->created);
+ $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->source_link($notice->source);
+ $twitter_status['source'] = $this->sourceLink($notice->source);
$twitter_status['id'] = intval($notice->id);
$replier_profile = null;
@@ -206,14 +249,14 @@ class TwitterapiAction extends Action
if ($include_user) {
# Don't get notice (recursive!)
- $twitter_user = $this->twitter_user_array($profile, false);
+ $twitter_user = $this->twitterUserArray($profile, false);
$twitter_status['user'] = $twitter_user;
}
return $twitter_status;
}
- function twitter_group_array($group)
+ function twitterGroupArray($group)
{
$twitter_group=array();
$twitter_group['id']=$group->id;
@@ -228,12 +271,12 @@ class TwitterapiAction extends Action
$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);
+ $twitter_group['created']=$this->dateTwitter($group->created);
+ $twitter_group['modified']=$this->dateTwitter($group->modified);
return $twitter_group;
}
- function twitter_rss_group_array($group)
+ function twitterRssGroupArray($group)
{
$entry = array();
$entry['content']=$group->description;
@@ -251,7 +294,7 @@ class TwitterapiAction extends Action
return $entry;
}
- function twitter_rss_entry_array($notice)
+ function twitterRssEntryArray($notice)
{
$profile = $notice->getProfile();
$entry = array();
@@ -288,23 +331,6 @@ class TwitterapiAction extends Action
$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;
@@ -324,65 +350,20 @@ class TwitterapiAction extends Action
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)
+ function twitterRelationshipArray($source, $target)
{
$relationship = array();
$relationship['source'] =
- $this->relationship_details_array($source, $target);
+ $this->relationshipDetailsArray($source, $target);
$relationship['target'] =
- $this->relationship_details_array($target, $source);
+ $this->relationshipDetailsArray($target, $source);
return array('relationship' => $relationship);
}
- function relationship_details_array($source, $target)
+ function relationshipDetailsArray($source, $target)
{
$details = array();
@@ -409,14 +390,14 @@ class TwitterapiAction extends Action
return $details;
}
- function show_twitter_xml_relationship($relationship)
+ function showTwitterXmlRelationship($relationship)
{
$this->elementStart('relationship');
foreach($relationship as $element => $value) {
if ($element == 'source' || $element == 'target') {
$this->elementStart($element);
- $this->show_xml_relationship_details($value);
+ $this->showXmlRelationshipDetails($value);
$this->elementEnd($element);
}
}
@@ -424,26 +405,26 @@ class TwitterapiAction extends Action
$this->elementEnd('relationship');
}
- function show_xml_relationship_details($details)
+ function showXmlRelationshipDetails($details)
{
foreach($details as $element => $value) {
$this->element($element, null, $value);
}
}
- function show_twitter_xml_status($twitter_status)
+ function showTwitterXmlStatus($twitter_status)
{
$this->elementStart('status');
foreach($twitter_status as $element => $value) {
switch ($element) {
case 'user':
- $this->show_twitter_xml_user($twitter_status['user']);
+ $this->showTwitterXmlUser($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']);
+ $this->showXmlAttachments($twitter_status['attachments']);
break;
default:
$this->element($element, null, $value);
@@ -452,7 +433,7 @@ class TwitterapiAction extends Action
$this->elementEnd('status');
}
- function show_twitter_xml_group($twitter_group)
+ function showTwitterXmlGroup($twitter_group)
{
$this->elementStart('group');
foreach($twitter_group as $element => $value) {
@@ -461,12 +442,12 @@ class TwitterapiAction extends Action
$this->elementEnd('group');
}
- function show_twitter_xml_user($twitter_user, $role='user')
+ function showTwitterXmlUser($twitter_user, $role='user')
{
$this->elementStart($role);
foreach($twitter_user as $element => $value) {
if ($element == 'status') {
- $this->show_twitter_xml_status($twitter_user['status']);
+ $this->showTwitterXmlStatus($twitter_user['status']);
} else {
$this->element($element, null, $value);
}
@@ -474,7 +455,7 @@ class TwitterapiAction extends Action
$this->elementEnd($role);
}
- function show_xml_attachments($attachments) {
+ function showXmlAttachments($attachments) {
if (!empty($attachments)) {
$this->elementStart('attachments', array('type' => 'array'));
foreach ($attachments as $attachment) {
@@ -488,7 +469,7 @@ class TwitterapiAction extends Action
}
}
- function show_twitter_rss_item($entry)
+ function showTwitterRssItem($entry)
{
$this->elementStart('item');
$this->element('title', null, $entry['title']);
@@ -502,7 +483,7 @@ class TwitterapiAction extends Action
$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);
@@ -512,93 +493,59 @@ class TwitterapiAction extends Action
$this->elementEnd('item');
}
- function show_json_objects($objects)
+ function showJsonObjects($objects)
{
print(json_encode($objects));
}
- function show_single_xml_status($notice)
+ function showSingleXmlStatus($notice)
{
- $this->init_document('xml');
- $twitter_status = $this->twitter_status_array($notice);
- $this->show_twitter_xml_status($twitter_status);
- $this->end_document('xml');
+ $this->initDocument('xml');
+ $twitter_status = $this->twitterStatusArray($notice);
+ $this->showTwitterXmlStatus($twitter_status);
+ $this->endDocument('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');
+ $this->initDocument('json');
+ $status = $this->twitterStatusArray($notice);
+ $this->showJsonObjects($status);
+ $this->endDocument('json');
}
- 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)
+ function showXmlTimeline($notice)
{
- $this->init_document('xml');
+ $this->initDocument('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);
+ $twitter_status = $this->twitterStatusArray($n);
+ $this->showTwitterXmlStatus($twitter_status);
}
} else {
while ($notice->fetch()) {
- $twitter_status = $this->twitter_status_array($notice);
- $this->show_twitter_xml_status($twitter_status);
+ $twitter_status = $this->twitterStatusArray($notice);
+ $this->showTwitterXmlStatus($twitter_status);
}
}
$this->elementEnd('statuses');
- $this->end_document('xml');
+ $this->endDocument('xml');
}
- function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
+ function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null)
{
- $this->init_document('rss');
+ $this->initDocument('rss');
$this->element('title', null, $title);
$this->element('link', null, $link);
if (!is_null($suplink)) {
- # For FriendFeed's SUP protocol
+ // 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,
@@ -610,23 +557,23 @@ class TwitterapiAction extends Action
if (is_array($notice)) {
foreach ($notice as $n) {
- $entry = $this->twitter_rss_entry_array($n);
- $this->show_twitter_rss_item($entry);
+ $entry = $this->twitterRssEntryArray($n);
+ $this->showTwitterRssItem($entry);
}
} else {
while ($notice->fetch()) {
- $entry = $this->twitter_rss_entry_array($notice);
- $this->show_twitter_rss_item($entry);
+ $entry = $this->twitterRssEntryArray($notice);
+ $this->showTwitterRssItem($entry);
}
}
- $this->end_twitter_rss();
+ $this->endTwitterRss();
}
- function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
+ function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
{
- $this->init_document('atom');
+ $this->initDocument('atom');
$this->element('title', null, $title);
$this->element('id', null, $id);
@@ -657,14 +604,14 @@ class TwitterapiAction extends Action
}
}
- $this->end_document('atom');
+ $this->endDocument('atom');
}
- function show_rss_groups($group, $title, $link, $subtitle)
+ function showRssGroups($group, $title, $link, $subtitle)
{
- $this->init_document('rss');
+ $this->initDocument('rss');
$this->element('title', null, $title);
$this->element('link', null, $link);
@@ -674,23 +621,138 @@ class TwitterapiAction extends Action
if (is_array($group)) {
foreach ($group as $g) {
- $twitter_group = $this->twitter_rss_group_array($g);
- $this->show_twitter_rss_item($twitter_group);
+ $twitter_group = $this->twitterRssGroupArray($g);
+ $this->showTwitterRssItem($twitter_group);
}
} else {
while ($group->fetch()) {
- $twitter_group = $this->twitter_rss_group_array($group);
- $this->show_twitter_rss_item($twitter_group);
+ $twitter_group = $this->twitterRssGroupArray($group);
+ $this->showTwitterRssItem($twitter_group);
}
}
- $this->end_twitter_rss();
+ $this->endTwitterRss();
}
- function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
+
+ 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');
+ }
- $this->init_document('atom');
+ 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);
@@ -714,168 +776,151 @@ class TwitterapiAction extends Action
}
}
- $this->end_document('atom');
+ $this->endDocument('atom');
}
- function show_json_timeline($notice)
+ function showJsonTimeline($notice)
{
- $this->init_document('json');
+ $this->initDocument('json');
$statuses = array();
if (is_array($notice)) {
foreach ($notice as $n) {
- $twitter_status = $this->twitter_status_array($n);
+ $twitter_status = $this->twitterStatusArray($n);
array_push($statuses, $twitter_status);
}
} else {
while ($notice->fetch()) {
- $twitter_status = $this->twitter_status_array($notice);
+ $twitter_status = $this->twitterStatusArray($notice);
array_push($statuses, $twitter_status);
}
}
- $this->show_json_objects($statuses);
+ $this->showJsonObjects($statuses);
- $this->end_document('json');
+ $this->endDocument('json');
}
- function show_json_groups($group)
+ function showJsonGroups($group)
{
- $this->init_document('json');
+ $this->initDocument('json');
$groups = array();
if (is_array($group)) {
foreach ($group as $g) {
- $twitter_group = $this->twitter_group_array($g);
+ $twitter_group = $this->twitterGroupArray($g);
array_push($groups, $twitter_group);
}
} else {
while ($group->fetch()) {
- $twitter_group = $this->twitter_group_array($group);
+ $twitter_group = $this->twitterGroupArray($group);
array_push($groups, $twitter_group);
}
}
- $this->show_json_objects($groups);
+ $this->showJsonObjects($groups);
- $this->end_document('json');
+ $this->endDocument('json');
}
- function show_xml_groups($group)
+ function showXmlGroups($group)
{
- $this->init_document('xml');
+ $this->initDocument('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);
+ $twitter_group = $this->twitterGroupArray($g);
+ $this->showTwitterXmlGroup($twitter_group);
}
} else {
while ($group->fetch()) {
- $twitter_group = $this->twitter_group_array($group);
- $this->show_twitter_xml_group($twitter_group);
+ $twitter_group = $this->twitterGroupArray($group);
+ $this->showTwitterXmlGroup($twitter_group);
}
}
$this->elementEnd('groups');
- $this->end_document('xml');
+ $this->endDocument('xml');
}
- function show_twitter_xml_users($user)
+ function showTwitterXmlUsers($user)
{
- $this->init_document('xml');
+ $this->initDocument('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');
+ foreach ($user as $u) {
+ $twitter_user = $this->twitterUserArray($u);
+ $this->showTwitterXmlUser($twitter_user);
}
} else {
while ($user->fetch()) {
- $twitter_user = $this->twitter_user_array($user);
- $this->show_twitter_xml_user($twitter_user);
+ $twitter_user = $this->twitterUserArray($user);
+ $this->showTwitterXmlUser($twitter_user);
}
}
$this->elementEnd('users');
- $this->end_document('xml');
+ $this->endDocument('xml');
}
- function show_json_users($user)
+ function showJsonUsers($user)
{
- $this->init_document('json');
+ $this->initDocument('json');
$users = array();
if (is_array($user)) {
foreach ($user as $u) {
- $twitter_user = $this->twitter_user_array($u);
+ $twitter_user = $this->twitterUserArray($u);
array_push($users, $twitter_user);
}
} else {
while ($user->fetch()) {
- $twitter_user = $this->twitter_user_array($user);
+ $twitter_user = $this->twitterUserArray($user);
array_push($users, $twitter_user);
}
}
- $this->show_json_objects($users);
+ $this->showJsonObjects($users);
- $this->end_document('json');
+ $this->endDocument('json');
}
- function show_single_json_group($group)
+ function showSingleJsonGroup($group)
{
- $this->init_document('json');
- $twitter_group = $this->twitter_group_array($group);
- $this->show_json_objects($twitter_group);
- $this->end_document('json');
+ $this->initDocument('json');
+ $twitter_group = $this->twitterGroupArray($group);
+ $this->showJsonObjects($twitter_group);
+ $this->endDocument('json');
}
- function show_single_xml_group($group)
+ function showSingleXmlGroup($group)
{
- $this->init_document('xml');
- $twitter_group = $this->twitter_group_array($group);
- $this->show_twitter_xml_group($twitter_group);
- $this->end_document('xml');
+ $this->initDocument('xml');
+ $twitter_group = $this->twitterGroupArray($group);
+ $this->showTwitterXmlGroup($twitter_group);
+ $this->endDocument('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)
+ function dateTwitter($dt)
{
- $t = strtotime($dt);
- return date("D M d H:i:s O Y", $t);
+ $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');
}
- // 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')
+ function initDocument($type='xml')
{
switch ($type) {
case 'xml':
@@ -893,11 +938,11 @@ class TwitterapiAction extends Action
break;
case 'rss':
header("Content-Type: application/rss+xml; charset=utf-8");
- $this->init_twitter_rss();
+ $this->initTwitterRss();
break;
case 'atom':
header('Content-Type: application/atom+xml; charset=utf-8');
- $this->init_twitter_atom();
+ $this->initTwitterAtom();
break;
default:
$this->clientError(_('Not a supported data format.'));
@@ -907,7 +952,7 @@ class TwitterapiAction extends Action
return;
}
- function end_document($type='xml')
+ function endDocument($type='xml')
{
switch ($type) {
case 'xml':
@@ -922,10 +967,10 @@ class TwitterapiAction extends Action
}
break;
case 'rss':
- $this->end_twitter_rss();
+ $this->endTwitterRss();
break;
case 'atom':
- $this->end_twitter_rss();
+ $this->endTwitterRss();
break;
default:
$this->clientError(_('Not a supported data format.'));
@@ -934,56 +979,69 @@ class TwitterapiAction extends Action
return;
}
- function clientError($msg, $code = 400, $content_type = 'json')
+ function clientError($msg, $code = 400, $format = 'xml')
{
+ $action = $this->trimmed('action');
- static $status = array(400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed');
+ 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("User error '$code' on '$action': $msg", __FILE__);
+ common_debug("Server error '$code' on '$action': $msg", __FILE__);
- if (!array_key_exists($code, $status)) {
+ if (!array_key_exists($code, ServerErrorAction::$status)) {
$code = 400;
}
- $status_string = $status[$code];
+ $status_string = ServerErrorAction::$status[$code];
+
header('HTTP/1.1 '.$code.' '.$status_string);
if ($content_type == 'xml') {
- $this->init_document('xml');
+ $this->initDocument('xml');
$this->elementStart('hash');
$this->element('error', null, $msg);
$this->element('request', null, $_SERVER['REQUEST_URI']);
$this->elementEnd('hash');
- $this->end_document('xml');
+ $this->endDocument('xml');
} else {
- $this->init_document('json');
+ $this->initDocument('json');
$error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
print(json_encode($error_array));
- $this->end_document('json');
+ $this->endDocument('json');
}
-
}
- function init_twitter_rss()
+ function initTwitterRss()
{
$this->startXML();
$this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom'));
@@ -991,14 +1049,14 @@ class TwitterapiAction extends Action
Event::handle('StartApiRss', array($this));
}
- function end_twitter_rss()
+ function endTwitterRss()
{
$this->elementEnd('channel');
$this->elementEnd('rss');
$this->endXML();
}
- function init_twitter_atom()
+ function initTwitterAtom()
{
$this->startXML();
// FIXME: don't hardcode the language here!
@@ -1008,21 +1066,21 @@ class TwitterapiAction extends Action
Event::handle('StartApiAtom', array($this));
}
- function end_twitter_atom()
+ function endTwitterAtom()
{
$this->elementEnd('feed');
$this->endXML();
}
- function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
+ function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
{
- $profile_array = $this->twitter_user_array($profile, $includeStatuses);
+ $profile_array = $this->twitterUserArray($profile, $includeStatuses);
switch ($content_type) {
case 'xml':
- $this->show_twitter_xml_user($profile_array);
+ $this->showTwitterXmlUser($profile_array);
break;
case 'json':
- $this->show_json_objects($profile_array);
+ $this->showJsonObjects($profile_array);
break;
default:
$this->clientError(_('Not a supported data format.'));
@@ -1031,7 +1089,7 @@ class TwitterapiAction extends Action
return;
}
- function get_user($id, $apidata=null)
+ function getTargetUser($id)
{
if (empty($id)) {
@@ -1052,7 +1110,7 @@ class TwitterapiAction extends Action
return User::staticGet('nickname', $nickname);
} else {
// Fall back to trying the currently authenticated user
- return $apidata['user'];
+ return $this->auth_user;
}
} else if (is_numeric($id)) {
@@ -1063,10 +1121,9 @@ class TwitterapiAction extends Action
}
}
- function get_group($id, $apidata=null)
+ function getTargetGroup($id)
{
if (empty($id)) {
-
if (is_numeric($this->arg('id'))) {
return User_group::staticGet($this->arg('id'));
} else if ($this->arg('id')) {
@@ -1091,21 +1148,7 @@ class TwitterapiAction extends Action
}
}
- 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)
+ function sourceLink($source)
{
$source_name = _($source);
switch ($source) {
diff --git a/lib/apiauth.php b/lib/apiauth.php
new file mode 100644
index 000000000..2f2e44a26
--- /dev/null
+++ b/lib/apiauth.php
@@ -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
index 000000000..2d29c1ddd
--- /dev/null
+++ b/lib/apibareauth.php
@@ -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
diff --git a/lib/clienterroraction.php b/lib/clienterroraction.php
index 7d007a756..1b98a1064 100644
--- a/lib/clienterroraction.php
+++ b/lib/clienterroraction.php
@@ -46,28 +46,28 @@ require_once INSTALLDIR.'/lib/error.php';
*/
class ClientErrorAction extends ErrorAction
{
+ static $status = array(400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed');
+
function __construct($message='Error', $code=400)
{
parent::__construct($message, $code);
-
- $this->status = array(400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed');
$this->default = 400;
}
@@ -91,9 +91,4 @@ class ClientErrorAction extends ErrorAction
$this->showPage();
}
-
- function title()
- {
- return $this->status[$this->code];
- }
}
diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php
index 6e4340e5d..60fc4c3c4 100644
--- a/lib/commandinterpreter.php
+++ b/lib/commandinterpreter.php
@@ -28,7 +28,7 @@ class CommandInterpreter
# XXX: localise
$text = preg_replace('/\s+/', ' ', trim($text));
- list($cmd, $arg) = explode(' ', $text, 2);
+ list($cmd, $arg) = $this->split_arg($text);
# We try to support all the same commands as Twitter, see
# http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
@@ -43,7 +43,7 @@ class CommandInterpreter
return new HelpCommand($user);
case 'on':
if ($arg) {
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -54,7 +54,7 @@ class CommandInterpreter
}
case 'off':
if ($arg) {
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -74,7 +74,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -84,7 +84,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -95,7 +95,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -106,7 +106,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -117,7 +117,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -128,7 +128,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if (!$extra) {
return null;
} else {
@@ -138,7 +138,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -148,7 +148,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -158,7 +158,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -173,7 +173,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($other, $extra) = explode(' ', $arg, 2);
+ list($other, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else {
@@ -183,7 +183,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($word, $extra) = explode(' ', $arg, 2);
+ list($word, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else if ($word == 'off') {
@@ -195,7 +195,7 @@ class CommandInterpreter
if (!$arg) {
return null;
}
- list($word, $extra) = explode(' ', $arg, 2);
+ list($word, $extra) = $this->split_arg($arg);
if ($extra) {
return null;
} else if ($word == 'all') {
@@ -213,5 +213,17 @@ class CommandInterpreter
return false;
}
}
+
+ /**
+ * Split arguments without triggering a PHP notice warning
+ */
+ function split_arg($text)
+ {
+ $pieces = explode(' ', $text, 2);
+ if (count($pieces) == 1) {
+ $pieces[] = null;
+ }
+ return $pieces;
+ }
}
diff --git a/lib/common.php b/lib/common.php
index f7c31c792..e29456ed4 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -53,6 +53,7 @@ require_once('DB/DataObject/Cast.php'); # for dates
if (!function_exists('gettext')) {
require_once("php-gettext/gettext.inc");
}
+
require_once(INSTALLDIR.'/lib/language.php');
// This gets included before the config file, so that admin code and plugins
@@ -93,214 +94,17 @@ if (isset($path)) {
null;
}
-// default configuration, overwritten in config.php
+require_once(INSTALLDIR.'/lib/default.php');
+
+// Set config values initially to default values
+
+$config = $default;
-$config =
- array('site' =>
- array('name' => 'Just another StatusNet microblog',
- 'server' => $_server,
- 'theme' => 'default',
- 'path' => $_path,
- 'logfile' => null,
- 'logo' => null,
- 'logdebug' => false,
- 'fancy' => false,
- 'locale_path' => INSTALLDIR.'/locale',
- 'language' => 'en_US',
- 'languages' => get_all_languages(),
- 'email' =>
- array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
- 'broughtby' => null,
- 'timezone' => 'UTC',
- 'broughtbyurl' => null,
- 'closed' => false,
- 'inviteonly' => false,
- 'private' => false,
- 'ssl' => 'never',
- 'sslserver' => null,
- 'shorturllength' => 30,
- 'dupelimit' => 60, # default for same person saying the same thing
- 'textlimit' => 140,
- ),
- 'syslog' =>
- array('appname' => 'statusnet', # for syslog
- 'priority' => 'debug', # XXX: currently ignored
- 'facility' => LOG_USER),
- 'queue' =>
- array('enabled' => false,
- 'subsystem' => 'db', # default to database, or 'stomp'
- 'stomp_server' => null,
- 'queue_basename' => 'statusnet',
- 'stomp_username' => null,
- 'stomp_password' => null,
- ),
- 'license' =>
- array('url' => 'http://creativecommons.org/licenses/by/3.0/',
- 'title' => 'Creative Commons Attribution 3.0',
- 'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'),
- 'mail' =>
- array('backend' => 'mail',
- 'params' => null),
- 'nickname' =>
- array('blacklist' => array(),
- 'featured' => array()),
- 'profile' =>
- array('banned' => array(),
- 'biolimit' => null),
- 'avatar' =>
- array('server' => null,
- 'dir' => INSTALLDIR . '/avatar/',
- 'path' => $_path . '/avatar/'),
- 'background' =>
- array('server' => null,
- 'dir' => INSTALLDIR . '/background/',
- 'path' => $_path . '/background/'),
- 'public' =>
- array('localonly' => true,
- 'blacklist' => array(),
- 'autosource' => array()),
- 'theme' =>
- array('server' => null,
- 'dir' => null,
- 'path'=> null),
- 'throttle' =>
- array('enabled' => false, // whether to throttle edits; false by default
- 'count' => 20, // number of allowed messages in timespan
- 'timespan' => 600), // timespan for throttling
- 'xmpp' =>
- array('enabled' => false,
- 'server' => 'INVALID SERVER',
- 'port' => 5222,
- 'user' => 'update',
- 'encryption' => true,
- 'resource' => 'uniquename',
- 'password' => 'blahblahblah',
- 'host' => null, # only set if != server
- 'debug' => false, # print extra debug info
- 'public' => array()), # JIDs of users who want to receive the public stream
- 'invite' =>
- array('enabled' => true),
- 'sphinx' =>
- array('enabled' => false,
- 'server' => 'localhost',
- 'port' => 3312),
- 'tag' =>
- array('dropoff' => 864000.0),
- 'popular' =>
- array('dropoff' => 864000.0),
- 'daemon' =>
- array('piddir' => '/var/run',
- 'user' => false,
- 'group' => false),
- 'emailpost' =>
- array('enabled' => true),
- 'sms' =>
- array('enabled' => true),
- 'twitter' =>
- array('enabled' => true),
- 'twitterbridge' =>
- array('enabled' => false),
- 'integration' =>
- array('source' => 'StatusNet', # source attribute for Twitter
- 'taguri' => $_server.',2009'), # base for tag URIs
- 'twitter' =>
- array('consumer_key' => null,
- 'consumer_secret' => null),
- 'memcached' =>
- array('enabled' => false,
- 'server' => 'localhost',
- 'base' => null,
- 'port' => 11211),
- 'ping' =>
- array('notify' => array()),
- 'inboxes' =>
- array('enabled' => true), # on by default for new sites
- 'newuser' =>
- array('default' => null,
- 'welcome' => null),
- 'snapshot' =>
- array('run' => 'web',
- 'frequency' => 10000,
- 'reporturl' => 'http://status.net/stats/report'),
- 'attachments' =>
- array('server' => null,
- 'dir' => INSTALLDIR . '/file/',
- 'path' => $_path . '/file/',
- 'supported' => array('image/png',
- 'image/jpeg',
- 'image/gif',
- 'image/svg+xml',
- 'audio/mpeg',
- 'audio/x-speex',
- 'application/ogg',
- 'application/pdf',
- 'application/vnd.oasis.opendocument.text',
- 'application/vnd.oasis.opendocument.text-template',
- 'application/vnd.oasis.opendocument.graphics',
- 'application/vnd.oasis.opendocument.graphics-template',
- 'application/vnd.oasis.opendocument.presentation',
- 'application/vnd.oasis.opendocument.presentation-template',
- 'application/vnd.oasis.opendocument.spreadsheet',
- 'application/vnd.oasis.opendocument.spreadsheet-template',
- 'application/vnd.oasis.opendocument.chart',
- 'application/vnd.oasis.opendocument.chart-template',
- 'application/vnd.oasis.opendocument.image',
- 'application/vnd.oasis.opendocument.image-template',
- 'application/vnd.oasis.opendocument.formula',
- 'application/vnd.oasis.opendocument.formula-template',
- 'application/vnd.oasis.opendocument.text-master',
- 'application/vnd.oasis.opendocument.text-web',
- 'application/x-zip',
- 'application/zip',
- 'text/plain',
- 'video/mpeg',
- 'video/mp4',
- 'video/quicktime',
- 'video/mpeg'),
- 'file_quota' => 5000000,
- 'user_quota' => 50000000,
- 'monthly_quota' => 15000000,
- 'uploads' => true,
- 'filecommand' => '/usr/bin/file',
- ),
- 'group' =>
- array('maxaliases' => 3,
- 'desclimit' => null),
- 'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
- 'search' =>
- array('type' => 'fulltext'),
- 'sessions' =>
- array('handle' => false, // whether to handle sessions ourselves
- 'debug' => false), // debugging output for sessions
- 'design' =>
- array('backgroundcolor' => null, // null -> 'use theme default'
- 'contentcolor' => null,
- 'sidebarcolor' => null,
- 'textcolor' => null,
- 'linkcolor' => null,
- 'backgroundimage' => null,
- 'disposition' => null),
- 'notice' =>
- array('contentlimit' => null),
- 'message' =>
- array('contentlimit' => null),
- 'http' =>
- array('client' => 'curl'), // XXX: should this be the default?
- );
+// default configuration, overwritten in config.php
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
-$config['db'] =
- array('database' => 'YOU HAVE TO SET THIS IN config.php',
- 'schema_location' => INSTALLDIR . '/classes',
- 'class_location' => INSTALLDIR . '/classes',
- 'require_prefix' => 'classes/',
- 'class_prefix' => '',
- 'mirror' => null,
- 'utf8' => true,
- 'db_driver' => 'DB', # XXX: JanRain libs only work with DB
- 'quote_identifiers' => false,
- 'type' => 'mysql' );
+$config['db'] = $default['db'];
// Backward compatibility
@@ -427,6 +231,12 @@ require_once INSTALLDIR.'/lib/serverexception.php';
Config::loadSettings();
+// XXX: if plugins should check the schema at runtime, do that here.
+
+if ($config['db']['schemacheck'] == 'runtime') {
+ Event::handle('CheckSchema');
+}
+
// XXX: other formats here
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
diff --git a/lib/default.php b/lib/default.php
new file mode 100644
index 000000000..329b041e9
--- /dev/null
+++ b/lib/default.php
@@ -0,0 +1,232 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Default settings for core configuration
+ *
+ * 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 Config
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2008-9 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/
+ */
+
+$default =
+ array('site' =>
+ array('name' => 'Just another StatusNet microblog',
+ 'server' => $_server,
+ 'theme' => 'default',
+ 'path' => $_path,
+ 'logfile' => null,
+ 'logo' => null,
+ 'logdebug' => false,
+ 'fancy' => false,
+ 'locale_path' => INSTALLDIR.'/locale',
+ 'language' => 'en_US',
+ 'languages' => get_all_languages(),
+ 'email' =>
+ array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
+ 'broughtby' => null,
+ 'timezone' => 'UTC',
+ 'broughtbyurl' => null,
+ 'closed' => false,
+ 'inviteonly' => false,
+ 'private' => false,
+ 'ssl' => 'never',
+ 'sslserver' => null,
+ 'shorturllength' => 30,
+ 'dupelimit' => 60, # default for same person saying the same thing
+ 'textlimit' => 140,
+ ),
+ 'db' =>
+ array('database' => 'YOU HAVE TO SET THIS IN config.php',
+ 'schema_location' => INSTALLDIR . '/classes',
+ 'class_location' => INSTALLDIR . '/classes',
+ 'require_prefix' => 'classes/',
+ 'class_prefix' => '',
+ 'mirror' => null,
+ 'utf8' => true,
+ 'db_driver' => 'DB', # XXX: JanRain libs only work with DB
+ 'quote_identifiers' => false,
+ 'type' => 'mysql',
+ 'schemacheck' => 'runtime'), // 'runtime' or 'script'
+ 'syslog' =>
+ array('appname' => 'statusnet', # for syslog
+ 'priority' => 'debug', # XXX: currently ignored
+ 'facility' => LOG_USER),
+ 'queue' =>
+ array('enabled' => false,
+ 'subsystem' => 'db', # default to database, or 'stomp'
+ 'stomp_server' => null,
+ 'queue_basename' => 'statusnet',
+ 'stomp_username' => null,
+ 'stomp_password' => null,
+ ),
+ 'license' =>
+ array('url' => 'http://creativecommons.org/licenses/by/3.0/',
+ 'title' => 'Creative Commons Attribution 3.0',
+ 'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'),
+ 'mail' =>
+ array('backend' => 'mail',
+ 'params' => null),
+ 'nickname' =>
+ array('blacklist' => array(),
+ 'featured' => array()),
+ 'profile' =>
+ array('banned' => array(),
+ 'biolimit' => null),
+ 'avatar' =>
+ array('server' => null,
+ 'dir' => INSTALLDIR . '/avatar/',
+ 'path' => $_path . '/avatar/'),
+ 'background' =>
+ array('server' => null,
+ 'dir' => INSTALLDIR . '/background/',
+ 'path' => $_path . '/background/'),
+ 'public' =>
+ array('localonly' => true,
+ 'blacklist' => array(),
+ 'autosource' => array()),
+ 'theme' =>
+ array('server' => null,
+ 'dir' => null,
+ 'path'=> null),
+ 'throttle' =>
+ array('enabled' => false, // whether to throttle edits; false by default
+ 'count' => 20, // number of allowed messages in timespan
+ 'timespan' => 600), // timespan for throttling
+ 'xmpp' =>
+ array('enabled' => false,
+ 'server' => 'INVALID SERVER',
+ 'port' => 5222,
+ 'user' => 'update',
+ 'encryption' => true,
+ 'resource' => 'uniquename',
+ 'password' => 'blahblahblah',
+ 'host' => null, # only set if != server
+ 'debug' => false, # print extra debug info
+ 'public' => array()), # JIDs of users who want to receive the public stream
+ 'invite' =>
+ array('enabled' => true),
+ 'sphinx' =>
+ array('enabled' => false,
+ 'server' => 'localhost',
+ 'port' => 3312),
+ 'tag' =>
+ array('dropoff' => 864000.0),
+ 'popular' =>
+ array('dropoff' => 864000.0),
+ 'daemon' =>
+ array('piddir' => '/var/run',
+ 'user' => false,
+ 'group' => false),
+ 'emailpost' =>
+ array('enabled' => true),
+ 'sms' =>
+ array('enabled' => true),
+ 'twitterbridge' =>
+ array('enabled' => false),
+ 'integration' =>
+ array('source' => 'StatusNet', # source attribute for Twitter
+ 'taguri' => $_server.',2009'), # base for tag URIs
+ 'twitter' =>
+ array('enabled' => true,
+ 'consumer_key' => null,
+ 'consumer_secret' => null),
+ 'memcached' =>
+ array('enabled' => false,
+ 'server' => 'localhost',
+ 'base' => null,
+ 'port' => 11211),
+ 'ping' =>
+ array('notify' => array()),
+ 'inboxes' =>
+ array('enabled' => true), # on by default for new sites
+ 'newuser' =>
+ array('default' => null,
+ 'welcome' => null),
+ 'snapshot' =>
+ array('run' => 'web',
+ 'frequency' => 10000,
+ 'reporturl' => 'http://status.net/stats/report'),
+ 'attachments' =>
+ array('server' => null,
+ 'dir' => INSTALLDIR . '/file/',
+ 'path' => $_path . '/file/',
+ 'supported' => array('image/png',
+ 'image/jpeg',
+ 'image/gif',
+ 'image/svg+xml',
+ 'audio/mpeg',
+ 'audio/x-speex',
+ 'application/ogg',
+ 'application/pdf',
+ 'application/vnd.oasis.opendocument.text',
+ 'application/vnd.oasis.opendocument.text-template',
+ 'application/vnd.oasis.opendocument.graphics',
+ 'application/vnd.oasis.opendocument.graphics-template',
+ 'application/vnd.oasis.opendocument.presentation',
+ 'application/vnd.oasis.opendocument.presentation-template',
+ 'application/vnd.oasis.opendocument.spreadsheet',
+ 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'application/vnd.oasis.opendocument.chart',
+ 'application/vnd.oasis.opendocument.chart-template',
+ 'application/vnd.oasis.opendocument.image',
+ 'application/vnd.oasis.opendocument.image-template',
+ 'application/vnd.oasis.opendocument.formula',
+ 'application/vnd.oasis.opendocument.formula-template',
+ 'application/vnd.oasis.opendocument.text-master',
+ 'application/vnd.oasis.opendocument.text-web',
+ 'application/x-zip',
+ 'application/zip',
+ 'text/plain',
+ 'video/mpeg',
+ 'video/mp4',
+ 'video/quicktime',
+ 'video/mpeg'),
+ 'file_quota' => 5000000,
+ 'user_quota' => 50000000,
+ 'monthly_quota' => 15000000,
+ 'uploads' => true,
+ 'filecommand' => '/usr/bin/file',
+ ),
+ 'group' =>
+ array('maxaliases' => 3,
+ 'desclimit' => null),
+ 'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
+ 'search' =>
+ array('type' => 'fulltext'),
+ 'sessions' =>
+ array('handle' => false, // whether to handle sessions ourselves
+ 'debug' => false), // debugging output for sessions
+ 'design' =>
+ array('backgroundcolor' => null, // null -> 'use theme default'
+ 'contentcolor' => null,
+ 'sidebarcolor' => null,
+ 'textcolor' => null,
+ 'linkcolor' => null,
+ 'backgroundimage' => null,
+ 'disposition' => null),
+ 'notice' =>
+ array('contentlimit' => null),
+ 'message' =>
+ array('contentlimit' => null),
+ 'http' =>
+ array('client' => 'curl'), // XXX: should this be the default?
+ );
diff --git a/lib/deleteaction.php b/lib/deleteaction.php
deleted file mode 100644
index f702820c6..000000000
--- a/lib/deleteaction.php
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Base class for deleting things
- *
- * 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 Personal
- * @package StatusNet
- * @author Evan Prodromou <evan@status.net>
- * @author Sarven Capadisli <csarven@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);
-}
-
-class DeleteAction extends Action
-{
- var $user = null;
- var $notice = null;
- var $profile = null;
- var $user_profile = null;
-
- function prepare($args)
- {
- parent::prepare($args);
-
- $this->user = common_current_user();
- $notice_id = $this->trimmed('notice');
- $this->notice = Notice::staticGet($notice_id);
-
- if (!$this->notice) {
- common_user_error(_('No such notice.'));
- exit;
- }
-
- $this->profile = $this->notice->getProfile();
- $this->user_profile = $this->user->getProfile();
-
- return true;
- }
-
- function handle($args)
- {
- parent::handle($args);
-
- if (!common_logged_in()) {
- common_user_error(_('Not logged in.'));
- exit;
- } else if ($this->notice->profile_id != $this->user_profile->id) {
- common_user_error(_('Can\'t delete this notice.'));
- exit;
- }
- }
-
-}
diff --git a/lib/designsettings.php b/lib/designsettings.php
index fdc05562e..820d534f2 100644
--- a/lib/designsettings.php
+++ b/lib/designsettings.php
@@ -325,7 +325,6 @@ class DesignSettingsAction extends AccountSettingsAction
parent::showScripts();
$this->script('js/farbtastic/farbtastic.js');
- $this->script('js/farbtastic/farbtastic.go.js');
$this->script('js/userdesign.go.js');
$this->autofocus('design_background-image_file');
diff --git a/lib/error.php b/lib/error.php
index 0c521db08..6a9b76be1 100644
--- a/lib/error.php
+++ b/lib/error.php
@@ -44,9 +44,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/
class ErrorAction extends Action
{
+ static $status = array();
+
var $code = null;
var $message = null;
- var $status = null;
var $default = null;
function __construct($message, $code, $output='php://output', $indent=true)
@@ -88,9 +89,10 @@ class ErrorAction extends Action
*
* @return page title
*/
+
function title()
{
- return $this->message;
+ return self::$status[$this->code];
}
function isReadOnly($args)
diff --git a/lib/facebookaction.php b/lib/facebookaction.php
index 411f79594..3f3a8d3b0 100644
--- a/lib/facebookaction.php
+++ b/lib/facebookaction.php
@@ -468,11 +468,11 @@ class FacebookAction extends Action
$replyto = $this->trimmed('inreplyto');
- $notice = Notice::saveNew($user->id, $content,
- 'web', 1, ($replyto == 'false') ? null : $replyto);
-
- if (is_string($notice)) {
- $this->showPage($notice);
+ try {
+ $notice = Notice::saveNew($user->id, $content,
+ 'web', 1, ($replyto == 'false') ? null : $replyto);
+ } catch (Exception $e) {
+ $this->showPage($e->getMessage());
return;
}
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
index aa01f6b1d..ce83295fb 100644
--- a/lib/htmloutputter.php
+++ b/lib/htmloutputter.php
@@ -109,11 +109,13 @@ class HTMLOutputter extends XMLOutputter
header('Content-Type: '.$type);
$this->extraHeaders();
- if( ! substr($type,0,strlen('text/html'))=='text/html' ){
- // Browsers don't like it when <?xml it output for non-xhtml documents
+ if (preg_match("/.*\/.*xml/", $type)) {
+ // Required for XML documents
$this->xw->startDocument('1.0', 'UTF-8');
}
- $this->xw->writeDTD('html');
+ $this->xw->writeDTD('html',
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
$language = $this->getLanguage();
@@ -425,16 +427,12 @@ class HTMLOutputter extends XMLOutputter
function autofocus($id)
{
$this->elementStart('script', array('type' => 'text/javascript'));
- $this->raw('
- <!--
- $(document).ready(function() {
- var el = $("#' . $id . '");
- if (el.length) {
- el.focus();
- }
- });
- -->
- ');
+ $this->raw('/*<![CDATA[*/'.
+ ' $(document).ready(function() {'.
+ ' var el = $("#' . $id . '");'.
+ ' if (el.length) { el.focus(); }'.
+ ' });'.
+ ' /*]]>*/');
$this->elementEnd('script');
}
}
diff --git a/lib/mail.php b/lib/mail.php
index df585406c..5bf4d7425 100644
--- a/lib/mail.php
+++ b/lib/mail.php
@@ -551,9 +551,9 @@ function mail_notify_fave($other, $user, $notice)
common_init_locale($other->language);
- $subject = sprintf(_('%s added your notice as a favorite'), $bestname);
+ $subject = sprintf(_('%s (@%s) added your notice as a favorite'), $bestname, $user->nickname);
- $body = sprintf(_("%1\$s just added your notice from %2\$s".
+ $body = sprintf(_("%1\$s (@%7\$s) just added your notice from %2\$s".
" as one of their favorites.\n\n" .
"The URL of your notice is:\n\n" .
"%3\$s\n\n" .
@@ -570,7 +570,8 @@ function mail_notify_fave($other, $user, $notice)
$notice->content,
common_local_url('showfavorites',
array('nickname' => $user->nickname)),
- common_config('site', 'name'));
+ common_config('site', 'name'),
+ $user->nickname);
common_init_locale();
mail_to_user($other, $subject, $body);
@@ -607,9 +608,9 @@ function mail_notify_attn($user, $notice)
$conversationUrl = null;
}
- $subject = sprintf(_('%s sent a notice to your attention'), $bestname);
+ $subject = sprintf(_('%s (@%s) sent a notice to your attention'), $bestname, $sender->nickname);
- $body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
+ $body = sprintf(_("%1\$s (@%9\$s) just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
"The notice is here:\n\n".
"\t%3\$s\n\n" .
"It reads:\n\n".
@@ -629,10 +630,11 @@ function mail_notify_attn($user, $notice)
$notice->content,//%4
$conversationUrl,//%5
common_local_url('newnotice',
- array('replyto' => $sender->nickname)),//%6
+ array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6
common_local_url('replies',
array('nickname' => $user->nickname)),//%7
- common_local_url('emailsettings'));//%8
+ common_local_url('emailsettings'), //%8
+ $sender->nickname); //%9
common_init_locale();
mail_to_user($user, $subject, $body);
diff --git a/lib/noticeform.php b/lib/noticeform.php
index cee46709e..9864d15eb 100644
--- a/lib/noticeform.php
+++ b/lib/noticeform.php
@@ -70,6 +70,12 @@ class NoticeForm extends Form
var $user = null;
/**
+ * The notice being replied to
+ */
+
+ var $inreplyto = null;
+
+ /**
* Constructor
*
* @param HTMLOutputter $out output channel
@@ -77,12 +83,13 @@ class NoticeForm extends Form
* @param string $content content to pre-fill
*/
- function __construct($out=null, $action=null, $content=null, $user=null)
+ function __construct($out=null, $action=null, $content=null, $user=null, $inreplyto=null)
{
parent::__construct($out);
$this->action = $action;
$this->content = $content;
+ $this->inreplyto = $inreplyto;
if ($user) {
$this->user = $user;
@@ -135,40 +142,44 @@ class NoticeForm extends Form
function formData()
{
- $this->out->element('label', array('for' => 'notice_data-text'),
- sprintf(_('What\'s up, %s?'), $this->user->nickname));
- // XXX: vary by defined max size
- $this->out->element('textarea', array('id' => 'notice_data-text',
- 'cols' => 35,
- 'rows' => 4,
- 'name' => 'status_textarea'),
- ($this->content) ? $this->content : '');
-
- $contentLimit = Notice::maxContent();
-
- $this->out->element('script', array('type' => 'text/javascript'),
- 'maxLength = ' . $contentLimit . ';');
-
- if ($contentLimit > 0) {
- $this->out->elementStart('dl', 'form_note');
- $this->out->element('dt', null, _('Available characters'));
- $this->out->element('dd', array('id' => 'notice_text-count'),
- $contentLimit);
- $this->out->elementEnd('dl');
- }
-
- if (common_config('attachments', 'uploads')) {
- $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach'));
- $this->out->element('input', array('id' => 'notice_data-attach',
- 'type' => 'file',
- 'name' => 'attach',
- 'title' => _('Attach a file')));
- $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
- }
- if ($this->action) {
- $this->out->hidden('notice_return-to', $this->action, 'returnto');
+ if (Event::handle('StartShowNoticeFormData', array($this))) {
+ $this->out->element('label', array('for' => 'notice_data-text'),
+ sprintf(_('What\'s up, %s?'), $this->user->nickname));
+ // XXX: vary by defined max size
+ $this->out->element('textarea', array('id' => 'notice_data-text',
+ 'cols' => 35,
+ 'rows' => 4,
+ 'name' => 'status_textarea'),
+ ($this->content) ? $this->content : '');
+
+ $contentLimit = Notice::maxContent();
+
+ $this->out->element('script', array('type' => 'text/javascript'),
+ 'maxLength = ' . $contentLimit . ';');
+
+ if ($contentLimit > 0) {
+ $this->out->elementStart('dl', 'form_note');
+ $this->out->element('dt', null, _('Available characters'));
+ $this->out->element('dd', array('id' => 'notice_text-count'),
+ $contentLimit);
+ $this->out->elementEnd('dl');
+ }
+
+ if (common_config('attachments', 'uploads')) {
+ $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach'));
+ $this->out->element('input', array('id' => 'notice_data-attach',
+ 'type' => 'file',
+ 'name' => 'attach',
+ 'title' => _('Attach a file')));
+ $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
+ }
+ if ($this->action) {
+ $this->out->hidden('notice_return-to', $this->action, 'returnto');
+ }
+ $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto');
+
+ Event::handle('StartShowNoticeFormData', array($this));
}
- $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto');
}
/**
diff --git a/lib/noticelist.php b/lib/noticelist.php
index ec85e4a5c..6c296f82a 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -178,9 +178,12 @@ class NoticeListItem extends Widget
function show()
{
$this->showStart();
- $this->showNotice();
- $this->showNoticeInfo();
- $this->showNoticeOptions();
+ if (Event::handle('StartShowNoticeItem', array($this))) {
+ $this->showNotice();
+ $this->showNoticeInfo();
+ $this->showNoticeOptions();
+ Event::handle('EndShowNoticeItem', array($this));
+ }
$this->showEnd();
}
@@ -261,7 +264,7 @@ class NoticeListItem extends Widget
$attrs = array('href' => $this->profile->profileurl,
'class' => 'url');
if (!empty($this->profile->fullname)) {
- $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ') ';
+ $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')';
}
$this->out->elementStart('a', $attrs);
$this->showAvatar();
@@ -418,9 +421,17 @@ class NoticeListItem extends Widget
function showContext()
{
- // XXX: also show context if there are replies to this notice
- if (!empty($this->notice->conversation)
- && $this->notice->conversation != $this->notice->id) {
+ $hasConversation = false;
+ if( !empty($this->notice->conversation)
+ && $this->notice->conversation != $this->notice->id){
+ $hasConversation = true;
+ }else{
+ $conversation = Notice::conversationStream($this->notice->id, 1, 1);
+ if($conversation->N > 0){
+ $hasConversation = true;
+ }
+ }
+ if ($hasConversation){
$convurl = common_local_url('conversation',
array('id' => $this->notice->conversation));
$this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id,
@@ -442,7 +453,7 @@ class NoticeListItem extends Widget
{
if (common_logged_in()) {
$reply_url = common_local_url('newnotice',
- array('replyto' => $this->profile->nickname));
+ array('replyto' => $this->profile->nickname, 'inreplyto' => $this->notice->id));
$this->out->elementStart('a', array('href' => $reply_url,
'class' => 'notice_reply',
'title' => _('Reply to this notice')));
@@ -461,7 +472,10 @@ class NoticeListItem extends Widget
function showDeleteLink()
{
$user = common_current_user();
- if ($user && $this->notice->profile_id == $user->id) {
+
+ if (!empty($user) &&
+ ($this->notice->profile_id == $user->id || $user->hasRight(Right::deleteOthersNotice))) {
+
$deleteurl = common_local_url('deletenotice',
array('notice' => $this->notice->id));
$this->out->element('a', array('href' => $deleteurl,
diff --git a/lib/oauthstore.php b/lib/oauthstore.php
index e69a00f55..d617a7df7 100644
--- a/lib/oauthstore.php
+++ b/lib/oauthstore.php
@@ -156,7 +156,6 @@ class StatusNetOAuthDataStore extends OAuthDataStore
return $this->new_access_token($consumer);
}
-
/**
* Revoke specified OAuth token
*
@@ -363,9 +362,7 @@ class StatusNetOAuthDataStore extends OAuthDataStore
false,
null,
$omb_notice->getIdentifierURI());
- if (is_string($notice)) {
- throw new Exception($notice);
- }
+
common_broadcast_notice($notice, true);
}
diff --git a/lib/right.php b/lib/right.php
new file mode 100644
index 000000000..4e0096d46
--- /dev/null
+++ b/lib/right.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for user rights
+ *
+ * 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 Authorization
+ * @package StatusNet
+ * @author Evan Prodromou <evan@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);
+}
+
+/**
+ * class for rights
+ *
+ * Mostly for holding the rights constants
+ *
+ * @category Authorization
+ * @package StatusNet
+ * @author Evan Prodromou <evan@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 Right
+{
+ const deleteOthersNotice = 'deleteothersnotice';
+}
+
diff --git a/lib/router.php b/lib/router.php
index f122c2b64..a5b6a9a30 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -168,6 +168,10 @@ class Router
$m->connect('notice/new?replyto=:replyto',
array('action' => 'newnotice'),
array('replyto' => '[A-Za-z0-9_-]+'));
+ $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
+ array('action' => 'newnotice'),
+ array('replyto' => '[A-Za-z0-9_-]+'),
+ array('inreplyto' => '[0-9]+'));
$m->connect('notice/:notice/file',
array('action' => 'file'),
@@ -233,6 +237,10 @@ class Router
array('nickname' => '[a-zA-Z0-9]+'));
}
+ $m->connect('group/:nickname/foaf',
+ array('action' => 'foafgroup'),
+ array('nickname' => '[a-zA-Z0-9]+'));
+
$m->connect('group/:nickname/blocked',
array('action' => 'blockedfromgroup'),
array('nickname' => '[a-zA-Z0-9]+'));
@@ -258,22 +266,100 @@ class Router
// statuses API
- $m->connect('api/statuses/:method',
- array('action' => 'api',
- 'apiaction' => 'statuses'),
- array('method' => '(public_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|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',
@@ -282,93 +368,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
@@ -378,71 +470,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|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'));
diff --git a/lib/rssaction.php b/lib/rssaction.php
index dd0f1005a..faf6bec7d 100644
--- a/lib/rssaction.php
+++ b/lib/rssaction.php
@@ -78,25 +78,12 @@ class Rss10Action extends Action
function prepare($args)
{
parent::prepare($args);
+
$this->limit = (int) $this->trimmed('limit');
+
if ($this->limit == 0) {
$this->limit = DEFAULT_RSS_LIMIT;
}
- return true;
- }
-
- /**
- * Handle a request
- *
- * @param array $args Arguments from $_REQUEST
- *
- * @return void
- */
-
- function handle($args)
- {
- // Parent handling, including cache check
- parent::handle($args);
if (common_config('site', 'private')) {
if (!isset($_SERVER['PHP_AUTH_USER'])) {
@@ -122,8 +109,21 @@ class Rss10Action extends Action
}
}
- // Get the list of notices
- $this->notices = $this->getNotices($this->limit);
+ return true;
+ }
+
+ /**
+ * Handle a request
+ *
+ * @param array $args Arguments from $_REQUEST
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ // Parent handling, including cache check
+ parent::handle($args);
$this->showRss();
}
@@ -140,7 +140,7 @@ class Rss10Action extends Action
}
/**
- * Get the notices to output in this stream
+ * Get the notices to output in this stream.
*
* @return array an array of Notice objects sorted in reverse chron
*/
diff --git a/lib/schema.php b/lib/schema.php
new file mode 100644
index 000000000..1e0c1f3e9
--- /dev/null
+++ b/lib/schema.php
@@ -0,0 +1,680 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Database schema utilities
+ *
+ * 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 Database
+ * @package StatusNet
+ * @author Evan Prodromou <evan@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);
+}
+
+/**
+ * Class representing the database schema
+ *
+ * A class representing the database schema. Can be used to
+ * manipulate the schema -- especially for plugins and upgrade
+ * utilities.
+ *
+ * @category Database
+ * @package StatusNet
+ * @author Evan Prodromou <evan@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 Schema
+{
+ static $_single = null;
+ protected $conn = null;
+
+ /**
+ * Constructor. Only run once for singleton object.
+ */
+
+ protected function __construct()
+ {
+ // XXX: there should be an easier way to do this.
+ $user = new User();
+
+ $this->conn = $user->getDatabaseConnection();
+
+ $user->free();
+
+ unset($user);
+ }
+
+ /**
+ * Main public entry point. Use this to get
+ * the singleton object.
+ *
+ * @return Schema the (single) Schema object
+ */
+
+ static function get()
+ {
+ if (empty(self::$_single)) {
+ self::$_single = new Schema();
+ }
+ return self::$_single;
+ }
+
+ /**
+ * Returns a TableDef object for the table
+ * in the schema with the given name.
+ *
+ * Throws an exception if the table is not found.
+ *
+ * @param string $name Name of the table to get
+ *
+ * @return TableDef tabledef for that table.
+ */
+
+ public function getTableDef($name)
+ {
+ $res =& $this->conn->query('DESCRIBE ' . $name);
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ $td = new TableDef();
+
+ $td->name = $name;
+ $td->columns = array();
+
+ $row = array();
+
+ while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) {
+
+ $cd = new ColumnDef();
+
+ $cd->name = $row['Field'];
+
+ $packed = $row['Type'];
+
+ if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) {
+ $cd->type = $match[1];
+ $cd->size = $match[2];
+ } else {
+ $cd->type = $packed;
+ }
+
+ $cd->nullable = ($row['Null'] == 'YES') ? true : false;
+ $cd->key = $row['Key'];
+ $cd->default = $row['Default'];
+ $cd->extra = $row['Extra'];
+
+ $td->columns[] = $cd;
+ }
+
+ return $td;
+ }
+
+ /**
+ * Gets a ColumnDef object for a single column.
+ *
+ * Throws an exception if the table is not found.
+ *
+ * @param string $table name of the table
+ * @param string $column name of the column
+ *
+ * @return ColumnDef definition of the column or null
+ * if not found.
+ */
+
+ public function getColumnDef($table, $column)
+ {
+ $td = $this->getTableDef($table);
+
+ foreach ($td->columns as $cd) {
+ if ($cd->name == $column) {
+ return $cd;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates a table with the given names and columns.
+ *
+ * @param string $name Name of the table
+ * @param array $columns Array of ColumnDef objects
+ * for new table.
+ *
+ * @return boolean success flag
+ */
+
+ public function createTable($name, $columns)
+ {
+ $uniques = array();
+ $primary = array();
+ $indices = array();
+
+ $sql = "CREATE TABLE $name (\n";
+
+ for ($i = 0; $i < count($columns); $i++) {
+
+ $cd =& $columns[$i];
+
+ if ($i > 0) {
+ $sql .= ",\n";
+ }
+
+ $sql .= $this->_columnSql($cd);
+
+ switch ($cd->key) {
+ case 'UNI':
+ $uniques[] = $cd->name;
+ break;
+ case 'PRI':
+ $primary[] = $cd->name;
+ break;
+ case 'MUL':
+ $indices[] = $cd->name;
+ break;
+ }
+ }
+
+ if (count($primary) > 0) { // it really should be...
+ $sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")";
+ }
+
+ foreach ($uniques as $u) {
+ $sql .= ",\nunique index {$name}_{$u}_idx ($u)";
+ }
+
+ foreach ($indices as $i) {
+ $sql .= ",\nindex {$name}_{$i}_idx ($i)";
+ }
+
+ $sql .= "); ";
+
+ $res =& $this->conn->query($sql);
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Drops a table from the schema
+ *
+ * Throws an exception if the table is not found.
+ *
+ * @param string $name Name of the table to drop
+ *
+ * @return boolean success flag
+ */
+
+ public function dropTable($name)
+ {
+ $res =& $this->conn->query("DROP TABLE $name");
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds an index to a table.
+ *
+ * If no name is provided, a name will be made up based
+ * on the table name and column names.
+ *
+ * Throws an exception on database error, esp. if the table
+ * does not exist.
+ *
+ * @param string $table Name of the table
+ * @param array $columnNames Name of columns to index
+ * @param string $name (Optional) name of the index
+ *
+ * @return boolean success flag
+ */
+
+ public function createIndex($table, $columnNames, $name=null)
+ {
+ if (!is_array($columnNames)) {
+ $columnNames = array($columnNames);
+ }
+
+ if (empty($name)) {
+ $name = "$table_".implode("_", $columnNames)."_idx";
+ }
+
+ $res =& $this->conn->query("ALTER TABLE $table ".
+ "ADD INDEX $name (".
+ implode(",", $columnNames).")");
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Drops a named index from a table.
+ *
+ * @param string $table name of the table the index is on.
+ * @param string $name name of the index
+ *
+ * @return boolean success flag
+ */
+
+ public function dropIndex($table, $name)
+ {
+ $res =& $this->conn->query("ALTER TABLE $table DROP INDEX $name");
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds a column to a table
+ *
+ * @param string $table name of the table
+ * @param ColumnDef $columndef Definition of the new
+ * column.
+ *
+ * @return boolean success flag
+ */
+
+ public function addColumn($table, $columndef)
+ {
+ $sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef);
+
+ $res =& $this->conn->query($sql);
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Modifies a column in the schema.
+ *
+ * The name must match an existing column and table.
+ *
+ * @param string $table name of the table
+ * @param ColumnDef $columndef new definition of the column.
+ *
+ * @return boolean success flag
+ */
+
+ public function modifyColumn($table, $columndef)
+ {
+ $sql = "ALTER TABLE $table MODIFY COLUMN " .
+ $this->_columnSql($columndef);
+
+ $res =& $this->conn->query($sql);
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Drops a column from a table
+ *
+ * The name must match an existing column.
+ *
+ * @param string $table name of the table
+ * @param string $columnName name of the column to drop
+ *
+ * @return boolean success flag
+ */
+
+ public function dropColumn($table, $columnName)
+ {
+ $sql = "ALTER TABLE $table DROP COLUMN $columnName";
+
+ $res =& $this->conn->query($sql);
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensures that a table exists with the given
+ * name and the given column definitions.
+ *
+ * If the table does not yet exist, it will
+ * create the table. If it does exist, it will
+ * alter the table to match the column definitions.
+ *
+ * @param string $tableName name of the table
+ * @param array $columns array of ColumnDef
+ * objects for the table
+ *
+ * @return boolean success flag
+ */
+
+ public function ensureTable($tableName, $columns)
+ {
+ // XXX: DB engine portability -> toilet
+
+ try {
+ $td = $this->getTableDef($tableName);
+ } catch (Exception $e) {
+ if (preg_match('/no such table/', $e->getMessage())) {
+ return $this->createTable($tableName, $columns);
+ } else {
+ throw $e;
+ }
+ }
+
+ $cur = $this->_names($td->columns);
+ $new = $this->_names($columns);
+
+ $toadd = array_diff($new, $cur);
+ $todrop = array_diff($cur, $new);
+ $same = array_intersect($new, $cur);
+ $tomod = array();
+
+ foreach ($same as $m) {
+ $curCol = $this->_byName($td->columns, $m);
+ $newCol = $this->_byName($columns, $m);
+
+ if (!$newCol->equals($curCol)) {
+ $tomod[] = $newCol->name;
+ }
+ }
+
+ if (count($toadd) + count($todrop) + count($tomod) == 0) {
+ // nothing to do
+ return true;
+ }
+
+ // For efficiency, we want this all in one
+ // query, instead of using our methods.
+
+ $phrase = array();
+
+ foreach ($toadd as $columnName) {
+ $cd = $this->_byName($columns, $columnName);
+
+ $phrase[] = 'ADD COLUMN ' . $this->_columnSql($cd);
+ }
+
+ foreach ($todrop as $columnName) {
+ $phrase[] = 'DROP COLUMN ' . $columnName;
+ }
+
+ foreach ($tomod as $columnName) {
+ $cd = $this->_byName($columns, $columnName);
+
+ $phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd);
+ }
+
+ $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase);
+
+ $res =& $this->conn->query($sql);
+
+ if (PEAR::isError($res)) {
+ throw new Exception($res->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the array of names from an array of
+ * ColumnDef objects.
+ *
+ * @param array $cds array of ColumnDef objects
+ *
+ * @return array strings for name values
+ */
+
+ private function _names($cds)
+ {
+ $names = array();
+
+ foreach ($cds as $cd) {
+ $names[] = $cd->name;
+ }
+
+ return $names;
+ }
+
+ /**
+ * Get a ColumnDef from an array matching
+ * name.
+ *
+ * @param array $cds Array of ColumnDef objects
+ * @param string $name Name of the column
+ *
+ * @return ColumnDef matching item or null if no match.
+ */
+
+ private function _byName($cds, $name)
+ {
+ foreach ($cds as $cd) {
+ if ($cd->name == $name) {
+ return $cd;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the proper SQL for creating or
+ * altering a column.
+ *
+ * Appropriate for use in CREATE TABLE or
+ * ALTER TABLE statements.
+ *
+ * @param ColumnDef $cd column to create
+ *
+ * @return string correct SQL for that column
+ */
+
+ private function _columnSql($cd)
+ {
+ $sql = "{$cd->name} ";
+
+ if (!empty($cd->size)) {
+ $sql .= "{$cd->type}({$cd->size}) ";
+ } else {
+ $sql .= "{$cd->type} ";
+ }
+
+ if (!empty($cd->default)) {
+ $sql .= "default {$cd->default} ";
+ } else {
+ $sql .= ($cd->nullable) ? "null " : "not null ";
+ }
+
+ return $sql;
+ }
+}
+
+/**
+ * A class encapsulating the structure of a table.
+ *
+ * @category Database
+ * @package StatusNet
+ * @author Evan Prodromou <evan@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 TableDef
+{
+ /** name of the table */
+ public $name;
+ /** array of ColumnDef objects for the columns. */
+ public $columns;
+}
+
+/**
+ * A class encapsulating the structure of a column in a table.
+ *
+ * @category Database
+ * @package StatusNet
+ * @author Evan Prodromou <evan@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 ColumnDef
+{
+ /** name of the column. */
+ public $name;
+ /** type of column, e.g. 'int', 'varchar' */
+ public $type;
+ /** size of the column. */
+ public $size;
+ /** boolean flag; can it be null? */
+ public $nullable;
+ /**
+ * type of key: null = no key; 'PRI' => primary;
+ * 'UNI' => unique key; 'MUL' => multiple values.
+ */
+ public $key;
+ /** default value if any. */
+ public $default;
+ /** 'extra' stuff. Returned by MySQL, largely
+ * unused. */
+ public $extra;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name name of the column
+ * @param string $type type of the column
+ * @param int $size size of the column
+ * @param boolean $nullable can this be null?
+ * @param string $key type of key
+ * @param value $default default value
+ * @param value $extra unused
+ */
+
+ function __construct($name=null, $type=null, $size=null,
+ $nullable=true, $key=null, $default=null,
+ $extra=null)
+ {
+ $this->name = strtolower($name);
+ $this->type = strtolower($type);
+ $this->size = $size+0;
+ $this->nullable = $nullable;
+ $this->key = $key;
+ $this->default = $default;
+ $this->extra = $extra;
+ }
+
+ /**
+ * Compares this columndef with another to see
+ * if they're functionally equivalent.
+ *
+ * @param ColumnDef $other column to compare
+ *
+ * @return boolean true if equivalent, otherwise false.
+ */
+
+ function equals($other)
+ {
+ return ($this->name == $other->name &&
+ $this->_typeMatch($other) &&
+ $this->_defaultMatch($other) &&
+ $this->_nullMatch($other) &&
+ $this->key == $other->key);
+ }
+
+ /**
+ * Does the type of this column match the
+ * type of the other column?
+ *
+ * Checks the type and size of a column. Tries
+ * to ignore differences between synonymous
+ * data types, like 'integer' and 'int'.
+ *
+ * @param ColumnDef $other other column to check
+ *
+ * @return boolean true if they're about equivalent
+ */
+
+ private function _typeMatch($other)
+ {
+ switch ($this->type) {
+ case 'integer':
+ case 'int':
+ return ($other->type == 'integer' ||
+ $other->type == 'int');
+ break;
+ default:
+ return ($this->type == $other->type &&
+ $this->size == $other->size);
+ }
+ }
+
+ /**
+ * Does the default behaviour of this column match
+ * the other?
+ *
+ * @param ColumnDef $other other column to check
+ *
+ * @return boolean true if defaults are effectively the same.
+ */
+
+ private function _defaultMatch($other)
+ {
+ return ((is_null($this->default) && is_null($other->default)) ||
+ ($this->default == $other->default));
+ }
+
+ /**
+ * Does the null behaviour of this column match
+ * the other?
+ *
+ * @param ColumnDef $other other column to check
+ *
+ * @return boolean true if these columns 'null' the same.
+ */
+
+ private function _nullMatch($other)
+ {
+ return ((!is_null($this->default) && !is_null($other->default) &&
+ $this->default == $other->default) ||
+ ($this->nullable == $other->nullable));
+ }
+}
diff --git a/lib/searchaction.php b/lib/searchaction.php
index 0d9f85a8f..130b28ff5 100644
--- a/lib/searchaction.php
+++ b/lib/searchaction.php
@@ -135,16 +135,21 @@ class SearchAction extends Action
}
function searchSuggestions($q) {
- $qe = urlencode($q);
- $message = sprintf(_(<<<E_O_T
+ $message = _(<<<E_O_T
* Make sure all words are spelled correctly.
* Try different keywords.
* Try more general keywords.
* Try fewer keywords.
+E_O_T
+);
+ if (!common_config('site', 'private')) {
+ $qe = urlencode($q);
+ $message .= sprintf(_(<<<E_O_T
+
You can also try your search on other engines:
-* [Twingly](http://www.twingly.com/search?q=%s&content=microblog&site=identi.ca)
+* [Twingly](http://www.twingly.com/search?q=%s&content=microblog&site=%%%%site.server%%%%)
* [Tweet scan](http://www.tweetscan.com/indexi.php?s=%s)
* [Google](http://www.google.com/search?q=site%%3A%%%%site.server%%%%+%s)
* [Yahoo](http://search.yahoo.com/search?p=site%%3A%%%%site.server%%%%+%s)
@@ -152,6 +157,7 @@ You can also try your search on other engines:
E_O_T
), $qe, $qe, $qe, $qe, $qe);
+ }
$this->elementStart('dl', array('id' => 'help_search', 'class' => 'help'));
$this->element('dt', null, _('Search help'));
$this->elementStart('dd', 'instructions');
diff --git a/lib/servererroraction.php b/lib/servererroraction.php
index c6400605e..0993a63bc 100644
--- a/lib/servererroraction.php
+++ b/lib/servererroraction.php
@@ -55,17 +55,17 @@ require_once INSTALLDIR.'/lib/error.php';
class ServerErrorAction extends ErrorAction
{
+ static $status = array(500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported');
+
function __construct($message='Error', $code=500)
{
parent::__construct($message, $code);
- $this->status = array(500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported');
-
$this->default = 500;
// Server errors must be logged.
@@ -93,9 +93,4 @@ class ServerErrorAction extends ErrorAction
$this->showPage();
}
-
- function title()
- {
- return $this->status[$this->code];
- }
}
diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php
index c5dc29d38..6cfe5bcbd 100644
--- a/lib/unqueuemanager.php
+++ b/lib/unqueuemanager.php
@@ -72,8 +72,13 @@ class UnQueueManager
require_once(INSTALLDIR.'/lib/jabber.php');
jabber_broadcast_notice($notice);
break;
+ case 'plugin':
+ Event::handle('HandleQueuedNotice', array(&$notice));
+ break;
default:
- throw ServerException("UnQueueManager: Unknown queue: $type");
+ if (Event::handle('UnqueueHandleNotice', array(&$notice, $queue))) {
+ throw ServerException("UnQueueManager: Unknown queue: $queue");
+ }
}
}
diff --git a/lib/util.php b/lib/util.php
index b74dc619c..be10647fc 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -391,10 +391,10 @@ function common_render_content($text, $notice)
{
$r = common_render_text($text);
$id = $notice->profile_id;
- $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
+ $r = preg_replace('/(^|[\s\.\,\:\;]+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
$r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
- $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
- $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
+ $r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
+ $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
return $r;
}
@@ -442,9 +442,9 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
')'.
'(?:'.
'(?:\:\d+)?'. //:port
- '(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"]*)?'. // /path
- '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\/]*)?'. // ?query string
- '(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\/\?\#]*)?'. // #fragment
+ '(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@]*)?'. // /path
+ '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@\/]*)?'. // ?query string
+ '(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\@/\?\#]*)?'. // #fragment
')(?<![\?\.\,\#\,])'.
')'.
'#ixu';
@@ -493,7 +493,7 @@ function callback_helper($matches, $callback, $notice_id) {
}while($original_url!=$url);
if(empty($notice_id)){
- $result = call_user_func_array($callback,$url);
+ $result = call_user_func_array($callback, array($url));
}else{
$result = call_user_func_array($callback, array(array($url,$notice_id)) );
}
@@ -522,21 +522,22 @@ function common_linkify($url) {
if(strpos($url, '@') !== false && strpos($url, ':') === false) {
//url is an email address without the mailto: protocol
- return XMLStringer::estring('a', array('href' => "mailto:$url", 'rel' => 'external'), $url);
- }
+ $canon = "mailto:$url";
+ $longurl = "mailto:$url";
+ }else{
- $canon = File_redirection::_canonUrl($url);
+ $canon = File_redirection::_canonUrl($url);
- $longurl_data = File_redirection::where($url);
- if (is_array($longurl_data)) {
- $longurl = $longurl_data['url'];
- } elseif (is_string($longurl_data)) {
- $longurl = $longurl_data;
- } else {
- throw new ServerException("Can't linkify url '$url'");
+ $longurl_data = File_redirection::where($canon);
+ if (is_array($longurl_data)) {
+ $longurl = $longurl_data['url'];
+ } elseif (is_string($longurl_data)) {
+ $longurl = $longurl_data;
+ } else {
+ throw new ServerException("Can't linkify url '$url'");
+ }
}
-
- $attrs = array('href' => $canon, 'rel' => 'external');
+ $attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external');
$is_attachment = false;
$attachment_id = null;
@@ -552,12 +553,13 @@ function common_linkify($url) {
}
if (!empty($f)) {
- if (isset($f->filename)) {
+ if ($f->isEnclosure()) {
$is_attachment = true;
$attachment_id = $f->id;
- } else { // if it has OEmbed info, it's an attachment, too
+ } else {
$foe = File_oembed::staticGet('file_id', $f->id);
if (!empty($foe)) {
+ // if it has OEmbed info, it's an attachment, too
$is_attachment = true;
$attachment_id = $f->id;
@@ -897,7 +899,8 @@ function common_enqueue_notice($notice)
'twitter',
'facebook',
'ping');
- static $allTransports = array('sms');
+
+ static $allTransports = array('sms', 'plugin');
$transports = $allTransports;
@@ -915,11 +918,16 @@ function common_enqueue_notice($notice)
}
}
- $qm = QueueManager::get();
+ if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) {
- foreach ($transports as $transport)
- {
- $qm->enqueue($notice, $transport);
+ $qm = QueueManager::get();
+
+ foreach ($transports as $transport)
+ {
+ $qm->enqueue($notice, $transport);
+ }
+
+ Event::handle('EndEnqueueNotice', array($notice, $transports));
}
return true;
@@ -990,7 +998,7 @@ function common_set_returnto($url)
function common_get_returnto()
{
common_ensure_session();
- return $_SESSION['returnto'];
+ return (array_key_exists('returnto', $_SESSION)) ? $_SESSION['returnto'] : null;
}
function common_timestamp()
@@ -1374,18 +1382,19 @@ function common_shorten_url($long_url)
$svc = $user->urlshorteningservice;
}
global $_shorteners;
- if(! $_shorteners[$svc]){
+ if (!isset($_shorteners[$svc])) {
//the user selected service doesn't exist, so default to ur1.ca
$svc = 'ur1.ca';
}
+ if (!isset($_shorteners[$svc])) {
+ // no shortener plugins installed.
+ return $long_url;
+ }
$reflectionObj = new ReflectionClass($_shorteners[$svc]['callInfo'][0]);
- $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]);
+ $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]);
$short_url = $short_url_service->shorten($long_url);
- if(substr($short_url,0,7)=='http://'){
- $short_url = substr($short_url,7);
- }
return $short_url;
}
diff --git a/plugins/Autocomplete/Autocomplete.js b/plugins/Autocomplete/Autocomplete.js
index dfadea004..3eff685a8 100644
--- a/plugins/Autocomplete/Autocomplete.js
+++ b/plugins/Autocomplete/Autocomplete.js
@@ -1,38 +1,37 @@
$(document).ready(function(){
- $.getJSON($('address .url')[0].href+'/api/statuses/friends.json?user_id=' + current_user['id'] + '&lite=true&callback=?',
- function(friends){
- $('#notice_data-text').autocomplete(friends, {
+ $('#notice_data-text').autocomplete($('address .url')[0].href+'/plugins/Autocomplete/autocomplete.json', {
multiple: true,
multipleSeparator: " ",
minChars: 1,
formatItem: function(row, i, max){
- return '@' + row.screen_name + ' (' + row.name + ')';
+ row = eval("(" + row + ")");
+ switch(row.type)
+ {
+ case 'user':
+ return row.nickname + ' (' + row.fullname + ')';
+ case 'group':
+ return row.nickname + ' (' + row.fullname + ')';
+ }
},
formatMatch: function(row, i, max){
- return '@' + row.screen_name;
+ row = eval("(" + row + ")");
+ switch(row.type)
+ {
+ case 'user':
+ return row.nickname;
+ case 'group':
+ return row.nickname;
+ }
},
formatResult: function(row){
- return '@' + row.screen_name;
+ row = eval("(" + row + ")");
+ switch(row.type)
+ {
+ case 'user':
+ return '@' + row.nickname;
+ case 'group':
+ return '!' + row.nickname;
+ }
}
});
- }
- );
- $.getJSON($('address .url')[0].href+'/api/statusnet/groups/list.json?user_id=' + current_user['id'] + '&callback=?',
- function(groups){
- $('#notice_data-text').autocomplete(groups, {
- multiple: true,
- multipleSeparator: " ",
- minChars: 1,
- formatItem: function(row, i, max){
- return '!' + row.nickname + ' (' + row.fullname + ')';
- },
- formatMatch: function(row, i, max){
- return '!' + row.nickname;
- },
- formatResult: function(row){
- return '!' + row.nickname;
- }
- });
- }
- );
});
diff --git a/plugins/Autocomplete/AutocompletePlugin.php b/plugins/Autocomplete/AutocompletePlugin.php
index b75397270..baaec73c1 100644
--- a/plugins/Autocomplete/AutocompletePlugin.php
+++ b/plugins/Autocomplete/AutocompletePlugin.php
@@ -31,6 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
+require_once(INSTALLDIR.'/plugins/Autocomplete/autocomplete.php');
+
class AutocompletePlugin extends Plugin
{
function __construct()
@@ -40,13 +42,6 @@ class AutocompletePlugin extends Plugin
function onEndShowScripts($action){
if (common_logged_in()) {
- $current_user = common_current_user();
- $js_string = <<<EOT
-<script type="text/javascript">
-var current_user = { id: '$current_user->id' };
-</script>
-EOT;
- $action->raw($js_string);
$action->script('plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.pack.js');
$action->script('plugins/Autocomplete/Autocomplete.js');
}
@@ -59,5 +54,12 @@ EOT;
}
}
+ function onRouterInitialized($m)
+ {
+ if (common_logged_in()) {
+ $m->connect('plugins/Autocomplete/autocomplete.json', array('action'=>'autocomplete'));
+ }
+ }
+
}
?>
diff --git a/plugins/Autocomplete/autocomplete.php b/plugins/Autocomplete/autocomplete.php
new file mode 100644
index 000000000..aa57b3915
--- /dev/null
+++ b/plugins/Autocomplete/autocomplete.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * List users for autocompletion
+ *
+ * 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 2008-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);
+}
+
+/**
+ * List users for autocompletion
+ *
+ * This is the form for adding a new g
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews <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/
+ */
+
+class AutocompleteAction extends Action
+{
+ private $result;
+
+ /**
+ * Last-modified date for page
+ *
+ * When was the content of this page last modified? Based on notice,
+ * profile, avatar.
+ *
+ * @return int last-modified date as unix timestamp
+ */
+ function lastModified()
+ {
+ $max=0;
+ foreach($this->users as $user){
+ $max = max($max,strtotime($user->modified),strtotime($user->profile->modified));
+ }
+ foreach($this->groups as $group){
+ $max = max($max,strtotime($group->modified));
+ }
+ return $max;
+ }
+
+ /**
+ * An entity tag for this page
+ *
+ * Shows the ETag for the page, based on the notice ID and timestamps
+ * for the notice, profile, and avatar. It's weak, since we change
+ * the date text "one hour ago", etc.
+ *
+ * @return string etag
+ */
+ function etag()
+ {
+ return '"' . implode(':', array($this->arg('action'),
+ crc32($this->arg('q')), //the actual string can have funny characters in we don't want showing up in the etag
+ $this->arg('limit'),
+ $this->lastModified())) . '"';
+ }
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+ $this->groups=array();
+ $this->users=array();
+ $q = $this->arg('q');
+ $limit = $this->arg('limit');
+ if($limit > 200) $limit=200; //prevent DOS attacks
+ if(substr($q,0,1)=='@'){
+ //user search
+ $q=substr($q,1);
+ $user = new User();
+ $user->limit($limit);
+ $user->whereAdd('nickname like \'' . trim($user->escape($q), '\'') . '%\'');
+ $user->find();
+ while($user->fetch()) {
+ $profile = Profile::staticGet($user->id);
+ $user->profile=$profile;
+ $this->users[]=$user;
+ }
+ }
+ if(substr($q,0,1)=='!'){
+ //group search
+ $q=substr($q,1);
+ $group = new User_group();
+ $group->limit($limit);
+ $group->whereAdd('nickname like \'' . trim($group->escape($q), '\'') . '%\'');
+ $group->find();
+ while($group->fetch()) {
+ $this->groups[]=$group;
+ }
+ }
+ return true;
+ }
+
+ function handle($args)
+ {
+ parent::handle($args);
+ $results = array();
+ foreach($this->users as $user){
+ $results[]=array('nickname' => $user->nickname, 'fullname'=> $user->profile->fullname, 'type'=>'user');
+ }
+ foreach($this->groups as $group){
+ $results[]=array('nickname' => $group->nickname, 'fullname'=> $group->fullname, 'type'=>'group');
+ }
+ foreach($results as $result) {
+ print json_encode($result) . "\n";
+ }
+ }
+}
diff --git a/plugins/Autocomplete/readme.txt b/plugins/Autocomplete/readme.txt
index 3272aa1ee..1db4c6565 100644
--- a/plugins/Autocomplete/readme.txt
+++ b/plugins/Autocomplete/readme.txt
@@ -1,5 +1,7 @@
Autocomplete allows users to autocomplete screen names in @ replies. When an "@" is typed into the notice text area, an autocomplete box is displayed populated with the user's friends' screen names.
+Note: This plugin doesn't work if the site is in Private mode, i.e. when $config['site']['private'] is set to true.
+
Installation
============
Add "addPlugin('Autocomplete');" to the bottom of your config.php
diff --git a/plugins/FBConnect/FBCLoginGroupNav.php b/plugins/FBConnect/FBCLoginGroupNav.php
index 6e12c2040..81b2520a4 100644
--- a/plugins/FBConnect/FBCLoginGroupNav.php
+++ b/plugins/FBConnect/FBCLoginGroupNav.php
@@ -78,16 +78,20 @@ class FBCLoginGroupNav extends Widget
// action => array('prompt', 'title')
$menu = array();
- $menu['login'] = array(_('Login'),
- _('Login with a username and password'));
-
- if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
- $menu['register'] = array(_('Register'),
- _('Sign up for a new account'));
+ if (!common_config('site','openidonly')) {
+ $menu['login'] = array(_('Login'),
+ _('Login with a username and password'));
+
+ if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
+ $menu['register'] = array(_('Register'),
+ _('Sign up for a new account'));
+ }
}
- $menu['openidlogin'] = array(_('OpenID'),
- _('Login or register with OpenID'));
+ if (common_config('openid', 'enabled')) {
+ $menu['openidlogin'] = array(_('OpenID'),
+ _('Login or register with OpenID'));
+ }
$menu['FBConnectLogin'] = array(_('Facebook'),
_('Login or register using Facebook'));
diff --git a/plugins/FBConnect/FBCSettingsNav.php b/plugins/FBConnect/FBCSettingsNav.php
index 29724d6bd..ed02371e2 100644
--- a/plugins/FBConnect/FBCSettingsNav.php
+++ b/plugins/FBConnect/FBCSettingsNav.php
@@ -77,32 +77,34 @@ class FBCSettingsNav extends Widget
$this->action->elementStart('dd');
# action => array('prompt', 'title')
- $menu =
- array('imsettings' =>
- array(_('IM'),
- _('Updates by instant messenger (IM)')),
- 'smssettings' =>
- array(_('SMS'),
- _('Updates by SMS')),
- 'twittersettings' =>
- array(_('Twitter'),
- _('Twitter integration options')),
- 'FBConnectSettings' =>
- array(_('Facebook'),
- _('Facebook Connect settings')));
+ $menu = array();
+ if (common_config('xmpp', 'enabled')) {
+ $menu['imsettings'] =
+ array(_('IM'),
+ _('Updates by instant messenger (IM)'));
+ }
+ if (common_config('sms', 'enabled')) {
+ $menu['smssettings'] =
+ array(_('SMS'),
+ _('Updates by SMS'));
+ }
+ if (common_config('twitter', 'enabled')) {
+ $menu['twittersettings'] =
+ array(_('Twitter'),
+ _('Twitter integration options'));
+ }
+ $menu['FBConnectSettings'] =
+ array(_('Facebook'),
+ _('Facebook Connect settings'));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
- if ($menuaction == 'imsettings' &&
- !common_config('xmpp', 'enabled')) {
- continue;
- }
$this->action->menuItem(common_local_url($menuaction),
- $menudesc[0],
- $menudesc[1],
- $action_name === $menuaction);
+ $menudesc[0],
+ $menudesc[1],
+ $action_name === $menuaction);
}
$this->action->elementEnd('ul');
diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php
index 593b49b4e..ff74aade4 100644
--- a/plugins/FBConnect/FBConnectPlugin.php
+++ b/plugins/FBConnect/FBConnectPlugin.php
@@ -232,6 +232,14 @@ class FBConnectPlugin extends Plugin
{
$user = common_current_user();
+ $connect = 'FBConnectSettings';
+ if (common_config('xmpp', 'enabled')) {
+ $connect = 'imsettings';
+ } else if (common_config('sms', 'enabled')) {
+ $connect = 'smssettings';
+ } else if (common_config('twitter', 'enabled')) {
+ $connect = 'twittersettings';
+ }
if (!empty($user)) {
@@ -266,13 +274,8 @@ class FBConnectPlugin extends Plugin
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
$action->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
- if (common_config('xmpp', 'enabled')) {
- $action->menuItem(common_local_url('imsettings'),
- _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
- } else {
- $action->menuItem(common_local_url('smssettings'),
- _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
- }
+ $action->menuItem(common_local_url($connect),
+ _('Connect'), _('Connect to services'), false, 'nav_connect');
if (common_config('invite', 'enabled')) {
$action->menuItem(common_local_url('invite'),
_('Invite'),
@@ -300,18 +303,30 @@ class FBConnectPlugin extends Plugin
}
}
else {
- if (!common_config('site', 'closed')) {
- $action->menuItem(common_local_url('register'),
- _('Register'), _('Create an account'), false, 'nav_register');
+ if (!common_config('site', 'openidonly')) {
+ if (!common_config('site', 'closed')) {
+ $action->menuItem(common_local_url('register'),
+ _('Register'), _('Create an account'), false, 'nav_register');
+ }
+ $action->menuItem(common_local_url('login'),
+ _('Login'), _('Login to the site'), false, 'nav_login');
+ } else {
+ $this->menuItem(common_local_url('openidlogin'),
+ _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
}
- $action->menuItem(common_local_url('login'),
- _('Login'), _('Login to the site'), false, 'nav_login');
}
$action->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help');
- $action->menuItem(common_local_url('peoplesearch'),
- _('Search'), _('Search for people or text'), false, 'nav_search');
+ if ($user || !common_config('site', 'private')) {
+ $action->menuItem(common_local_url('peoplesearch'),
+ _('Search'), _('Search for people or text'), false, 'nav_search');
+ }
+
+ // We are replacing the primary nav entirely; give other
+ // plugins a chance to handle it here.
+
+ Event::handle('EndPrimaryNav', array($action));
return false;
}
diff --git a/plugins/InfiniteScroll/InfiniteScrollPlugin.php b/plugins/InfiniteScroll/InfiniteScrollPlugin.php
index c955298cb..5928c007f 100644
--- a/plugins/InfiniteScroll/InfiniteScrollPlugin.php
+++ b/plugins/InfiniteScroll/InfiniteScrollPlugin.php
@@ -40,7 +40,7 @@ class InfiniteScrollPlugin extends Plugin
function onEndShowScripts($action)
{
- $action->script('plugins/InfiniteScroll/jquery.infinitescroll.min.js');
+ $action->script('plugins/InfiniteScroll/jquery.infinitescroll.js');
$action->script('plugins/InfiniteScroll/infinitescroll.js');
}
}
diff --git a/plugins/InfiniteScroll/infinitescroll.js b/plugins/InfiniteScroll/infinitescroll.js
index 6513072d0..ae4d53d09 100644
--- a/plugins/InfiniteScroll/infinitescroll.js
+++ b/plugins/InfiniteScroll/infinitescroll.js
@@ -1,6 +1,7 @@
jQuery(document).ready(function($){
$('notices_primary').infinitescroll({
debug: true,
+ infiniteScroll : false,
nextSelector : "li.nav_next a",
loadingImg : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif',
text : "<em>Loading the next set of posts...</em>",
@@ -12,4 +13,3 @@ jQuery(document).ready(function($){
NoticeAttachments();
});
});
-
diff --git a/plugins/InfiniteScroll/jquery.infinitescroll.js b/plugins/InfiniteScroll/jquery.infinitescroll.js
index 670686b0e..ec31bb086 100644
--- a/plugins/InfiniteScroll/jquery.infinitescroll.js
+++ b/plugins/InfiniteScroll/jquery.infinitescroll.js
@@ -92,14 +92,14 @@
if (props.isDuringAjax || props.isInvalidPage || props.isDone) return;
- if ( !isNearBottom(opts,props) ) return;
+ if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return;
// we dont want to fire the ajax multiple times
props.isDuringAjax = true;
// show the loading message and hide the previous/next links
props.loadingMsg.appendTo( opts.contentSelector ).show();
- $( opts.navSelector ).hide();
+ if(opts.infiniteScroll) $( opts.navSelector ).hide();
// increment the URL bit. e.g. /page/3/
props.currPage++;
@@ -205,10 +205,19 @@
}
});
- // bind scroll handler to element (if its a local scroll) or window
- $(opts.localMode ? this : window)
- .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } )
- .trigger('scroll.infscr'); // trigger the event, in case it's a short page
+ if(opts.infiniteScroll){
+ // bind scroll handler to element (if its a local scroll) or window
+ $(opts.localMode ? this : window)
+ .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } )
+ .trigger('scroll.infscr'); // trigger the event, in case it's a short page
+ }else{
+ $(opts.nextSelector).click(
+ function(){
+ infscrSetup(path,opts,props,callback);
+ return false;
+ }
+ );
+ }
return this;
@@ -222,6 +231,7 @@
$.infinitescroll = {
defaults : {
debug : false,
+ infiniteScroll : true,
preload : false,
nextSelector : "div.navigation a:first",
loadingImg : "http://www.infinite-scroll.com/loading.gif",
diff --git a/plugins/Meteor/meteorupdater.js b/plugins/Meteor/meteorupdater.js
index 2e688336f..9ce68775b 100644
--- a/plugins/Meteor/meteorupdater.js
+++ b/plugins/Meteor/meteorupdater.js
@@ -1,5 +1,6 @@
-// update the local timeline from a Meteor server
-//
+// Update the local timeline from a Meteor server
+// XXX: If @a is subscribed to @b, @a should get @b's notices in @a's Personal timeline.
+// Do Replies timeline.
var MeteorUpdater = function()
{
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index 91bddf381..a933a1155 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -222,4 +222,19 @@ class OpenIDPlugin extends Plugin
return true;
}
+
+ function onCheckSchema() {
+ $schema = Schema::get();
+ $schema->ensureTable('user_openid',
+ array(new ColumnDef('canonical', 'varchar',
+ '255', false, 'PRI'),
+ new ColumnDef('display', 'varchar',
+ '255', false),
+ new ColumnDef('user_id', 'integer',
+ null, false, 'MUL'),
+ new ColumnDef('created', 'datetime',
+ null, false),
+ new ColumnDef('modified', 'timestamp')));
+ return true;
+ }
}
diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php
new file mode 100644
index 000000000..ba87b266a
--- /dev/null
+++ b/plugins/Orbited/OrbitedPlugin.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Plugin to do "real time" updates using Orbited + STOMP
+ *
+ * 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 Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php';
+
+/**
+ * Plugin to do realtime updates using Orbited + STOMP
+ *
+ * This plugin pushes data to a STOMP server which is then served to the
+ * browser by the Orbited server.
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://laconi.ca/
+ */
+
+class OrbitedPlugin extends RealtimePlugin
+{
+ public $webserver = null;
+ public $webport = null;
+ public $channelbase = null;
+ public $stompserver = null;
+ public $stompport = null;
+ public $username = null;
+ public $password = null;
+ public $webuser = null;
+ public $webpass = null;
+
+ protected $con = null;
+
+ function onStartShowHeadElements($action)
+ {
+ // See http://orbited.org/wiki/Deployment#Cross-SubdomainDeployment
+ $action->element('script', null, ' document.domain = document.domain; ');
+ }
+
+ function _getScripts()
+ {
+ $scripts = parent::_getScripts();
+
+ $port = (is_null($this->webport)) ? 8000 : $this->webport;
+
+ $server = (is_null($this->webserver)) ? common_config('site', 'server') : $this->webserver;
+
+ $root = 'http://'.$server.(($port == 80) ? '':':'.$port);
+
+ $scripts[] = $root.'/static/Orbited.js';
+ $scripts[] = common_path('plugins/Orbited/orbitedextra.js');
+ $scripts[] = $root.'/static/protocols/stomp/stomp.js';
+ $scripts[] = common_path('plugins/Orbited/orbitedupdater.js');
+
+ return $scripts;
+ }
+
+ function _updateInitialize($timeline, $user_id)
+ {
+ $script = parent::_updateInitialize($timeline, $user_id);
+
+ $server = $this->_getStompServer();
+ $port = $this->_getStompPort();
+
+ return $script." OrbitedUpdater.init(\"$server\", $port, ".
+ "\"{$timeline}\", \"{$this->webuser}\", \"{$this->webpass}\");";
+ }
+
+ function _connect()
+ {
+ require_once(INSTALLDIR.'/extlib/Stomp.php');
+
+ $url = $this->_getStompUrl();
+
+ $this->con = new Stomp($url);
+
+ if ($this->con->connect($this->username, $this->password)) {
+ $this->log(LOG_INFO, "Connected.");
+ } else {
+ $this->log(LOG_ERR, 'Failed to connect to queue server');
+ throw new ServerException('Failed to connect to queue server');
+ }
+ }
+
+ function _publish($channel, $message)
+ {
+ $result = $this->con->send($channel,
+ json_encode($message));
+
+ return $result;
+ // TODO: parse and deal with result
+ }
+
+ function _disconnect()
+ {
+ $this->con->disconnect();
+ }
+
+ function _pathToChannel($path)
+ {
+ if (!empty($this->channelbase)) {
+ array_unshift($path, $this->channelbase);
+ }
+ return '/' . implode('/', $path);
+ }
+
+ function _getStompServer()
+ {
+ return (!is_null($this->stompserver)) ? $this->stompserver :
+ (!is_null($this->webserver)) ? $this->webserver :
+ common_config('site', 'server');
+ }
+
+ function _getStompPort()
+ {
+ return (!is_null($this->stompport)) ? $this->stompport : 61613;
+ }
+
+ function _getStompUrl()
+ {
+ $server = $this->_getStompServer();
+ $port = $this->_getStompPort();
+ return "tcp://$server:$port/";
+ }
+}
diff --git a/plugins/Orbited/orbitedextra.js b/plugins/Orbited/orbitedextra.js
new file mode 100644
index 000000000..47e5c0c80
--- /dev/null
+++ b/plugins/Orbited/orbitedextra.js
@@ -0,0 +1,2 @@
+TCPSocket = Orbited.TCPSocket;
+
diff --git a/plugins/Orbited/orbitedupdater.js b/plugins/Orbited/orbitedupdater.js
new file mode 100644
index 000000000..8c5ab3b73
--- /dev/null
+++ b/plugins/Orbited/orbitedupdater.js
@@ -0,0 +1,24 @@
+// Update the local timeline from a Orbited server
+
+var OrbitedUpdater = function()
+{
+ return {
+
+ init: function(server, port, timeline, username, password)
+ {
+ // set up stomp client.
+ stomp = new STOMPClient();
+
+ stomp.onmessageframe = function(frame) {
+ RealtimeUpdate.receive(JSON.parse(frame.body));
+ };
+
+ stomp.onconnectedframe = function() {
+ stomp.subscribe(timeline);
+ }
+
+ stomp.connect(server, port, username, password);
+ }
+ }
+}();
+
diff --git a/plugins/PiwikAnalyticsPlugin.php b/plugins/PiwikAnalyticsPlugin.php
index 85a24c132..54faa0bdb 100644
--- a/plugins/PiwikAnalyticsPlugin.php
+++ b/plugins/PiwikAnalyticsPlugin.php
@@ -38,30 +38,24 @@ if (!defined('STATUSNET')) {
* This plugin will spoot out the correct JavaScript spell to invoke
* Piwik Analytics on a page.
*
- * To use this plugin please add the following three lines to your config.php
+ * To use this plugin add the following to your config.php
*
- * require_once('plugins/PiwikAnalyticsPlugin.php');
- * $pa = new PiwikAnalyticsPlugin("example.com/piwik/","id");
+ * addPlugin('PiwikAnalytics', array('piwikroot' => 'example.com/piwik/',
+ * 'piwikId' => 'id'));
*
- * exchange example.com/piwik/ with the url to your piwik installation and
- * make sure you don't forget the final /
- * exchange id with the ID your statusnet installation has in your Piwik analytics
+ * Replace 'example.com/piwik/' with the URL to your Piwik installation and
+ * make sure you don't forget the final /.
+ * Replace 'id' with the ID your statusnet installation has in your Piwik
+ * analytics setup - for example '8'.
*
- * @category Plugin
- * @package StatusNet
- * @author Tobias Diekershoff <tobias.diekershoff@gmx.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 Event
*/
class PiwikAnalyticsPlugin extends Plugin
{
/** the base of your Piwik installation */
- var $piwikroot = null;
+ public $piwikroot = null;
/** the Piwik Id of your statusnet installation */
- var $piwikId = null;
+ public $piwikId = null;
/**
* constructor
@@ -73,7 +67,7 @@ class PiwikAnalyticsPlugin extends Plugin
function __construct($root=null, $id=null)
{
$this->piwikroot = $root;
- $this->piwikid = $id;
+ $this->piwikId = $id;
parent::__construct();
}
@@ -96,7 +90,7 @@ document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/ja
</script>
<script type="text/javascript">
try {
- var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 4);
+ var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", {$this->piwikId});
piwikTracker.trackPageView();
piwikTracker.enableLinkTracking();
} catch( err ) {}
@@ -108,4 +102,4 @@ ENDOFPIWIK;
$action->raw($piwikCode);
return true;
}
-} \ No newline at end of file
+}
diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
index 013a234d7..e1e82e352 100644
--- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php
+++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
@@ -31,7 +31,7 @@ if (!defined('STATUSNET')) {
exit(1);
}
-define('DEFAULT_HUB','http://2pubsubhubbub.appspot.com');
+define('DEFAULT_HUB','http://pubsubhubbub.appspot.com');
require_once(INSTALLDIR.'/plugins/PubSubHubBub/publisher.php');
@@ -59,7 +59,7 @@ class PubSubHubBubPlugin extends Plugin
$action->element('atom:link',array('rel'=>'hub','href'=>$this->hub),null);
}
- function onEndNoticeSave($notice){
+ function onHandleQueuedNotice($notice){
$publisher = new Publisher($this->hub);
$feeds = array();
diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php
index 82eca3d08..0c7c1240c 100644
--- a/plugins/Realtime/RealtimePlugin.php
+++ b/plugins/Realtime/RealtimePlugin.php
@@ -50,6 +50,11 @@ class RealtimePlugin extends Plugin
protected $favorurl = null;
protected $deleteurl = null;
+ /**
+ * When it's time to initialize the plugin, calculate and
+ * pass the URLs we need.
+ */
+
function onInitializePlugin()
{
$this->replyurl = common_local_url('newnotice');
@@ -57,29 +62,26 @@ class RealtimePlugin extends Plugin
// FIXME: need to find a better way to pass this pattern in
$this->deleteurl = common_local_url('deletenotice',
array('notice' => '0000000000'));
+ return true;
}
function onEndShowScripts($action)
{
- $path = null;
+ $timeline = $this->_getTimeline($action);
- switch ($action->trimmed('action')) {
- case 'public':
- $path = array('public');
- break;
- case 'tag':
- $tag = $action->trimmed('tag');
- if (!empty($tag)) {
- $path = array('tag', $tag);
- } else {
- return true;
- }
- break;
- default:
+ // If there's not a timeline on this page,
+ // just return true
+
+ if (empty($timeline)) {
return true;
}
- $timeline = $this->_pathToChannel($path);
+ $base = $action->selfUrl();
+ if (mb_strstr($base, '?')) {
+ $url = $base . '&realtime=1';
+ } else {
+ $url = $base . '?realtime=1';
+ }
$scripts = $this->_getScripts();
@@ -95,10 +97,22 @@ class RealtimePlugin extends Plugin
$user_id = 0;
}
+ if ($action->boolean('realtime')) {
+ $realtimeUI = ' RealtimeUpdate.initPopupWindow();';
+ }
+ else {
+ $iconurl = common_path('plugins/Realtime/icon_external.gif');
+ $realtimeUI = ' RealtimeUpdate.addPopup("'.$url.'", "'.$timeline.'", "'. $iconurl .'");';
+ }
+
$action->elementStart('script', array('type' => 'text/javascript'));
- $action->raw("$(document).ready(function() { ");
- $action->raw($this->_updateInitialize($timeline, $user_id));
- $action->raw(" });");
+
+ $script = ' $(document).ready(function() { '.
+ $realtimeUI.
+ $this->_updateInitialize($timeline, $user_id).
+ '}); ';
+ $action->raw($script);
+
$action->elementEnd('script');
return true;
@@ -108,13 +122,23 @@ class RealtimePlugin extends Plugin
{
$paths = array();
- // XXX: Add other timelines; this is just for the public one
+ // Add to the author's timeline
+
+ $user = User::staticGet('id', $notice->profile_id);
+
+ if (!empty($user)) {
+ $paths[] = array('showstream', $user->nickname);
+ }
+
+ // Add to the public timeline
if ($notice->is_local ||
($notice->is_local == 0 && !common_config('public', 'localonly'))) {
$paths[] = array('public');
}
+ // Add to the tags timeline
+
$tags = $this->getNoticeTags($notice);
if (!empty($tags)) {
@@ -123,6 +147,46 @@ class RealtimePlugin extends Plugin
}
}
+ // Add to inbox timelines
+ // XXX: do a join
+
+ $inbox = new Notice_inbox();
+ $inbox->notice_id = $notice->id;
+
+ if ($inbox->find()) {
+ while ($inbox->fetch()) {
+ $user = User::staticGet('id', $inbox->user_id);
+ $paths[] = array('all', $user->nickname);
+ }
+ }
+
+ // Add to the replies timeline
+
+ $reply = new Reply();
+ $reply->notice_id = $notice->id;
+
+ if ($reply->find()) {
+ while ($reply->fetch()) {
+ $user = User::staticGet('id', $reply->profile_id);
+ if (!empty($user)) {
+ $paths[] = array('replies', $user->nickname);
+ }
+ }
+ }
+
+ // Add to the group timeline
+ // XXX: join
+
+ $gi = new Group_inbox();
+ $gi->notice_id = $notice->id;
+
+ if ($gi->find()) {
+ while ($gi->fetch()) {
+ $ug = User_group::staticGet('id', $gi->group_id);
+ $paths[] = array('showgroup', $ug->nickname);
+ }
+ }
+
if (count($paths) > 0) {
$json = $this->noticeAsJson($notice);
@@ -140,18 +204,49 @@ class RealtimePlugin extends Plugin
return true;
}
+ function onStartShowBody($action)
+ {
+ $realtime = $action->boolean('realtime');
+ if (!$realtime) {
+ return true;
+ }
+
+ $action->elementStart('body',
+ (common_current_user()) ? array('id' => $action->trimmed('action'),
+ 'class' => 'user_in')
+ : array('id' => $action->trimmed('action')));
+
+ // XXX hack to deal with JS that tries to get the
+ // root url from page output
+
+ $action->elementStart('address');
+ $action->element('a', array('class' => 'url',
+ 'href' => common_local_url('public')),
+ '');
+ $action->elementEnd('address');
+
+ if (common_logged_in()) {
+ $action->showNoticeForm();
+ }
+
+ $action->showContentBlock();
+ $action->showScripts();
+ $action->elementEnd('body');
+ return false; // No default processing
+ }
+
function noticeAsJson($notice)
{
// 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']);
@@ -224,4 +319,41 @@ class RealtimePlugin extends Plugin
{
return '';
}
+
+ function _getTimeline($action)
+ {
+ $path = null;
+ $timeline = null;
+
+ $action_name = $action->trimmed('action');
+
+ switch ($action_name) {
+ case 'public':
+ $path = array('public');
+ break;
+ case 'tag':
+ $tag = $action->trimmed('tag');
+ if (!empty($tag)) {
+ $path = array('tag', $tag);
+ }
+ break;
+ case 'showstream':
+ case 'all':
+ case 'replies':
+ case 'showgroup':
+ $nickname = common_canonical_nickname($action->trimmed('nickname'));
+ if (!empty($nickname)) {
+ $path = array($action_name, $nickname);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!empty($path)) {
+ $timeline = $this->_pathToChannel($path);
+ }
+
+ return $timeline;
+ }
}
diff --git a/plugins/Realtime/icon_external.gif b/plugins/Realtime/icon_external.gif
new file mode 100644
index 000000000..c4118d53b
--- /dev/null
+++ b/plugins/Realtime/icon_external.gif
Binary files differ
diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js
index d55db5859..a75f17d8c 100644
--- a/plugins/Realtime/realtimeupdate.js
+++ b/plugins/Realtime/realtimeupdate.js
@@ -1,8 +1,8 @@
// add a notice encoded as JSON into the current timeline
//
+// TODO: i18n
RealtimeUpdate = {
-
_userid: 0,
_replyurl: '',
_favorurl: '',
@@ -10,27 +10,42 @@ RealtimeUpdate = {
init: function(userid, replyurl, favorurl, deleteurl)
{
- RealtimeUpdate._userid = userid;
- RealtimeUpdate._replyurl = replyurl;
- RealtimeUpdate._favorurl = favorurl;
- RealtimeUpdate._deleteurl = deleteurl;
+ RealtimeUpdate._userid = userid;
+ RealtimeUpdate._replyurl = replyurl;
+ RealtimeUpdate._favorurl = favorurl;
+ RealtimeUpdate._deleteurl = deleteurl;
+
+ $(window).blur(function() {
+ $('#notices_primary .notice').css({
+ 'border-top-color':$('#notices_primary .notice:last').css('border-top-color'),
+ 'border-top-style':'dotted'
+ });
+
+ $('#notices_primary .notice:first').css({
+ 'border-top-color':'#AAAAAA',
+ 'border-top-style':'solid'
+ });
+
+ return false;
+ });
},
receive: function(data)
{
- id = data.id;
-
- // Don't add it if it already exists
-
- if ($("#notice-"+id).length > 0) {
- return;
- }
-
- var noticeItem = RealtimeUpdate.makeNoticeItem(data);
- $("#notices_primary .notices").prepend(noticeItem, true);
- $("#notices_primary .notice:first").css({display:"none"});
- $("#notices_primary .notice:first").fadeIn(1000);
- NoticeReply();
+ setTimeout(function() {
+ id = data.id;
+
+ // Don't add it if it already exists
+ if ($("#notice-"+id).length > 0) {
+ return;
+ }
+
+ var noticeItem = RealtimeUpdate.makeNoticeItem(data);
+ $("#notices_primary .notices").prepend(noticeItem);
+ $("#notices_primary .notice:first").css({display:"none"});
+ $("#notices_primary .notice:first").fadeIn(1000);
+ NoticeReply();
+ }, 500);
},
makeNoticeItem: function(data)
@@ -50,30 +65,19 @@ RealtimeUpdate = {
"<p class=\"entry-content\">"+html+"</p>"+
"</div>"+
"<div class=\"entry-content\">"+
- "<dl class=\"timestamp\">"+
- "<dt>Published</dt>"+
- "<dd>"+
- "<a rel=\"bookmark\" href=\""+data['url']+"\" >"+
+ "<a class=\"timestamp\" rel=\"bookmark\" href=\""+data['url']+"\" >"+
"<abbr class=\"published\" title=\""+data['created_at']+"\">a few seconds ago</abbr>"+
"</a> "+
- "</dd>"+
- "</dl>"+
- "<dl class=\"device\">"+
- "<dt>From</dt> "+
- "<dd>"+source+"</dd>"+ // may have a link, I think
- "</dl>";
-
+ "<span class=\"source\">"+
+ "from "+
+ "<span class=\"device\">"+source+"</span>"+ // may have a link
+ "</span>";
if (data['in_reply_to_status_id']) {
- ni = ni+" <dl class=\"response\">"+
- "<dt>To</dt>"+
- "<dd>"+
- "<a href=\""+data['in_reply_to_status_url']+"\" rel=\"in-reply-to\">in reply to</a>"+
- "</dd>"+
- "</dl>";
+ ni = ni+" <a class=\"response\" href=\""+data['in_reply_to_status_url']+"\">in context</a>";
}
ni = ni+"</div>"+
- "<div class=\"notice-options\">";
+ "<div class=\"notice-options\">";
if (RealtimeUpdate._userid != 0) {
var input = $("form#form_notice fieldset input#token");
@@ -95,12 +99,12 @@ RealtimeUpdate = {
var ff;
ff = "<form id=\"favor-"+id+"\" class=\"form_favor\" method=\"post\" action=\""+RealtimeUpdate._favorurl+"\">"+
- "<fieldset>"+
- "<legend>Favor this notice</legend>"+ // XXX: i18n
+ "<fieldset>"+
+ "<legend>Favor this notice</legend>"+
"<input name=\"token-"+id+"\" type=\"hidden\" id=\"token-"+id+"\" value=\""+session_key+"\"/>"+
"<input name=\"notice\" type=\"hidden\" id=\"notice-n"+id+"\" value=\""+id+"\"/>"+
"<input type=\"submit\" id=\"favor-submit-"+id+"\" name=\"favor-submit-"+id+"\" class=\"submit\" value=\"Favor\" title=\"Favor this notice\"/>"+
- "</fieldset>"+
+ "</fieldset>"+
"</form>";
return ff;
},
@@ -108,28 +112,71 @@ RealtimeUpdate = {
makeReplyLink: function(id, nickname)
{
var rl;
- rl = "<dl class=\"notice_reply\">"+
- "<dt>Reply to this notice</dt>"+
- "<dd>"+
- "<a href=\""+RealtimeUpdate._replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span>"+
- "</a>"+
- "</dd>"+
- "</dl>";
+ rl = "<a class=\"notice_reply\" href=\""+RealtimeUpdate._replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span></a>";
return rl;
- },
+ },
makeDeleteLink: function(id)
{
var dl, delurl;
delurl = RealtimeUpdate._deleteurl.replace("0000000000", id);
- dl = "<dl class=\"notice_delete\">"+
- "<dt>Delete this notice</dt>"+
- "<dd>"+
- "<a href=\""+delurl+"\" title=\"Delete this notice\">Delete</a>"+
- "</dd>"+
- "</dl>";
+ dl = "<a class=\"notice_delete\" href=\""+delurl+"\" title=\"Delete this notice\">Delete</a>";
return dl;
},
+
+ addPopup: function(url, timeline, iconurl)
+ {
+ $('#notices_primary').css({'position':'relative'});
+ $('#notices_primary').prepend('<button id="realtime_timeline" title="Pop up in a window">Pop up</button>');
+
+ $('#realtime_timeline').css({
+ 'margin':'0 0 11px 0',
+ 'background':'transparent url('+ iconurl + ') no-repeat 0% 30%',
+ 'padding':'0 0 0 20px',
+ 'display':'block',
+ 'position':'absolute',
+ 'top':'-20px',
+ 'right':'0',
+ 'border':'none',
+ 'cursor':'pointer',
+ 'color':$("a").css("color"),
+ 'font-weight':'bold',
+ 'font-size':'1em'
+ });
+
+ $('#realtime_timeline').click(function() {
+ window.open(url,
+ timeline,
+ 'toolbar=no,resizable=yes,scrollbars=yes,status=yes');
+
+ return false;
+ });
+ },
+
+ initPopupWindow: function()
+ {
+ window.resizeTo(500, 550);
+ $('address').hide();
+ $('#content').css({'width':'93.5%'});
+
+ $('#form_notice').css({
+ 'margin':'18px 0 18px 1.795%',
+ 'width':'93%',
+ 'max-width':'451px'
+ });
+
+ $('#form_notice label[for=notice_data-text], h1').css({'display': 'none'});
+
+ $('.notices li:first-child').css({'border-top-color':'transparent'});
+
+ $('#form_notice label[for="notice_data-attach"], #form_notice #notice_data-attach').css({'top':'0'});
+
+ $('#form_notice #notice_data-attach').css({
+ 'left':'auto',
+ 'right':'0'
+ });
+ }
}
+
diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php
index b49e2e119..afc3f55ba 100644
--- a/plugins/TwitterBridge/twitter.php
+++ b/plugins/TwitterBridge/twitter.php
@@ -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/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php
index fd331fbdc..1040d72fb 100644
--- a/plugins/TwitterBridge/twitterbasicauthclient.php
+++ b/plugins/TwitterBridge/twitterbasicauthclient.php
@@ -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/
*
*/
diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README
index ce23a2695..b996f96cc 100644
--- a/plugins/recaptcha/README
+++ b/plugins/recaptcha/README
@@ -6,7 +6,7 @@ Use:
1. Get an API key from http://recaptcha.net
2. In config.php add:
-include_once('plugins/recaptcha.php');
+include_once('plugins/recaptcha/recaptcha.php');
$captcha = new recaptcha(publickey, privatekey, showErrors);
Changelog
diff --git a/scripts/checkschema.php b/scripts/checkschema.php
new file mode 100644
index 000000000..bf52abe15
--- /dev/null
+++ b/scripts/checkschema.php
@@ -0,0 +1,30 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$helptext = <<<END_OF_CHECKSCHEMA_HELP
+Gives plugins a chance to update the database schema.
+
+END_OF_CHECKSCHEMA_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+Event::handle('CheckSchema');
diff --git a/scripts/createsim.php b/scripts/createsim.php
index 71ed3bf72..1266a9700 100644
--- a/scripts/createsim.php
+++ b/scripts/createsim.php
@@ -101,7 +101,7 @@ function newSub($i)
$to = User::staticGet('nickname', $tunic);
- if (empty($from)) {
+ if (empty($to)) {
throw new Exception("Can't find user '$tunic'.");
}
diff --git a/scripts/deleteuser.php b/scripts/deleteuser.php
new file mode 100644
index 000000000..52389123c
--- /dev/null
+++ b/scripts/deleteuser.php
@@ -0,0 +1,68 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'i::n::y';
+$longoptions = array('id::nickname::yes');
+
+$helptext = <<<END_OF_DELETEUSER_HELP
+deleteuser.php [options]
+deletes a user from the database
+
+ -i --id ID of the user
+ -n --nickname nickname of the user
+ -y --yes do not wait for confirmation
+
+END_OF_DELETEUSER_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (have_option('i', 'id')) {
+ $id = get_option_value('i', 'id');
+ $user = User::staticGet('id', $id);
+ if (empty($user)) {
+ print "Can't find user with ID $id\n";
+ exit(1);
+ }
+} else if (have_option('n', 'nickname')) {
+ $nickname = get_option_value('n', 'nickname');
+ $user = User::staticGet('nickname', $nickname);
+ if (empty($user)) {
+ print "Can't find user with nickname '$nickname'\n";
+ exit(1);
+ }
+} else {
+ print "You must provide either an ID or a nickname.\n";
+ exit(1);
+}
+
+if (!have_option('y', 'yes')) {
+ print "About to PERMANENTLY delete user '{$user->nickname}' ({$user->id}). Are you sure? [y/N] ";
+ $response = fgets(STDIN);
+ if (strtolower(trim($response)) != 'y') {
+ print "Aborting.\n";
+ exit(0);
+ }
+}
+
+print "Deleting...";
+$user->delete();
+print "DONE.\n";
diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php
index 8f48e8e6f..6dd019712 100755
--- a/scripts/getvaliddaemons.php
+++ b/scripts/getvaliddaemons.php
@@ -35,20 +35,36 @@ ENDOFHELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
+$daemons = array();
+
+$daemons[] = INSTALLDIR.'/scripts/pluginqueuehandler.php';
+$daemons[] = INSTALLDIR.'/scripts/ombqueuehandler.php';
+$daemons[] = INSTALLDIR.'/scripts/facebookqueuehandler.php';
+$daemons[] = INSTALLDIR.'/scripts/pingqueuehandler.php';
+
if(common_config('xmpp','enabled')) {
- echo "xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php ";
- echo "xmppconfirmhandler.php ";
+ $daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php';
+ $daemons[] = INSTALLDIR.'/scripts/jabberqueuehandler.php';
+ $daemons[] = INSTALLDIR.'/scripts/publicqueuehandler.php';
+ $daemons[] = INSTALLDIR.'/scripts/xmppconfirmhandler.php';
}
+
if(common_config('twitterbridge','enabled')) {
- echo "twitterstatusfetcher.php ";
+ $daemons[] = INSTALLDIR.'/scripts/twitterstatusfetcher.php';
}
-echo "ombqueuehandler.php ";
+
if (common_config('twitter', 'enabled')) {
- echo "twitterqueuehandler.php ";
- echo "synctwitterfriends.php ";
+ $daemons[] = INSTALLDIR.'/scripts/twitterqueuehandler.php';
+ $daemons[] = INSTALLDIR.'/scripts/synctwitterfriends.php';
}
-echo "facebookqueuehandler.php ";
-echo "pingqueuehandler.php ";
+
if (common_config('sms', 'enabled')) {
- echo "smsqueuehandler.php ";
+ $daemons[] = INSTALLDIR.'/scripts/smsqueuehandler.php';
+}
+
+if (Event::handle('GetValidDaemons', array(&$daemons))) {
+ foreach ($daemons as $daemon) {
+ print $daemon . ' ';
+ }
+ print "\n";
}
diff --git a/scripts/maildaemon.php b/scripts/maildaemon.php
index 5705cfd50..586bef624 100755
--- a/scripts/maildaemon.php
+++ b/scripts/maildaemon.php
@@ -260,10 +260,11 @@ class MailerDaemon
function add_notice($user, $msg, $fileRecords)
{
- $notice = Notice::saveNew($user->id, $msg, 'mail');
- if (is_string($notice)) {
- $this->log(LOG_ERR, $notice);
- return $notice;
+ try {
+ $notice = Notice::saveNew($user->id, $msg, 'mail');
+ } catch (Exception $e) {
+ $this->log(LOG_ERR, $e->getMessage());
+ return $e->getMessage();
}
foreach($fileRecords as $fileRecord){
$this->attachFile($notice, $fileRecord);
diff --git a/scripts/pluginqueuehandler.php b/scripts/pluginqueuehandler.php
new file mode 100755
index 000000000..ae807db6a
--- /dev/null
+++ b/scripts/pluginqueuehandler.php
@@ -0,0 +1,58 @@
+#!/usr/bin/env php
+<?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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_OMB_HELP
+Daemon script for letting plugins handle stuff at queue time
+
+ -i --id Identity (default none)
+
+END_OF_OMB_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/queuehandler.php';
+
+class PluginQueueHandler extends QueueHandler
+{
+
+ function transport()
+ {
+ return 'plugin';
+ }
+
+ function handle_notice($notice)
+ {
+ Event::handle('HandleQueuedNotice', array(&$notice));
+ return true;
+ }
+}
+
+if (have_option('i', 'id')) {
+ $id = get_option_value('i', 'id');
+} else {
+ $id = null;
+}
+
+$handler = new PluginQueueHandler($id);
+$handler->runOnce();
diff --git a/actions/twitapinotifications.php b/scripts/showtable.php
index 0653e69ab..eb18a98e2 100644
--- a/actions/twitapinotifications.php
+++ b/scripts/showtable.php
@@ -1,6 +1,7 @@
+#!/usr/bin/env php
<?php
/*
- * StatusNet - the distributed open-source microblogging tool
+ * StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
@@ -17,24 +18,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-require_once(INSTALLDIR.'/lib/twitterapi.php');
+$helptext = <<<END_OF_SHOWTABLE_HELP
+showtable.php <tablename>
+Shows the structure of a table
-# This naming convention looks real sick
-class TwitapinotificationsAction extends TwitterapiAction
-{
+END_OF_SHOWTABLE_HELP;
- function follow($args, $apidata)
- {
- parent::handle($args);
- $this->serverError(_('API method under construction.'), $code=501);
- }
+require_once INSTALLDIR.'/scripts/commandline.inc';
- function leave($args, $apidata)
- {
- parent::handle($args);
- $this->serverError(_('API method under construction.'), $code=501);
- }
+if (count($args) != 1) {
+ show_help();
+}
-} \ No newline at end of file
+$name = $args[0];
+
+$schema = Schema::get();
+
+$td = $schema->getTableDef($name);
+
+print_r($td);
diff --git a/scripts/startdaemons.sh b/scripts/startdaemons.sh
index 298162673..5fb75414d 100755
--- a/scripts/startdaemons.sh
+++ b/scripts/startdaemons.sh
@@ -40,7 +40,7 @@ DAEMONS=`php $DIR/getvaliddaemons.php $ARGSG`
for f in $DAEMONS; do
printf "Starting $f...";
- php $DIR/$f $ARGSD
+ php $f $ARGSD
printf "DONE.\n"
done
diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php
index 1b1aec3e6..b2efc07c3 100755
--- a/scripts/xmppdaemon.php
+++ b/scripts/xmppdaemon.php
@@ -323,12 +323,15 @@ class XMPPDaemon extends Daemon
mb_strlen($content_shortened)));
return;
}
- $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
- if (is_string($notice)) {
- $this->log(LOG_ERR, $notice);
- $this->from_site($user->jabber, $notice);
+
+ try {
+ $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
+ } catch (Exception $e) {
+ $this->log(LOG_ERR, $e->getMessage());
+ $this->from_site($user->jabber, $e->getMessage());
return;
}
+
common_broadcast_notice($notice);
$this->log(LOG_INFO,
'Added notice ' . $notice->id . ' from user ' . $user->nickname);
diff --git a/tests/HashTagDetectionTests.php b/tests/HashTagDetectionTests.php
index aeac4a5e3..483d7135e 100644
--- a/tests/HashTagDetectionTests.php
+++ b/tests/HashTagDetectionTests.php
@@ -7,6 +7,7 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('STATUSNET', true);
+define('LACONICA', true);
require_once INSTALLDIR . '/lib/common.php';
diff --git a/tests/URLDetectionTest.php b/tests/URLDetectionTest.php
index 87b537646..45203bf6e 100644
--- a/tests/URLDetectionTest.php
+++ b/tests/URLDetectionTest.php
@@ -7,6 +7,7 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('STATUSNET', true);
+define('LACONICA', true);
require_once INSTALLDIR . '/lib/common.php';
@@ -27,68 +28,72 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
return array(
array('not a link :: no way',
'not a link :: no way'),
+ array('link http://www.somesite.com/xyz/35637563@N00/52803365/ link',
+ 'link <a href="http://www.somesite.com/xyz/35637563@N00/52803365/" title="http://www.somesite.com/xyz/35637563@N00/52803365/" rel="external">http://www.somesite.com/xyz/35637563@N00/52803365/</a> link'),
array('http://127.0.0.1',
- '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
+ '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
array('127.0.0.1',
- '<a href="http://127.0.0.1/" rel="external">127.0.0.1</a>'),
+ '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">127.0.0.1</a>'),
array('127.0.0.1:99',
- '<a href="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'),
+ '<a href="http://127.0.0.1:99/" title="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'),
array('127.0.0.1/Name:test.php',
- '<a href="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'),
+ '<a href="http://127.0.0.1/Name:test.php" title="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'),
array('127.0.0.1/~test',
- '<a href="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'),
+ '<a href="http://127.0.0.1/~test" title="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'),
array('127.0.0.1/+test',
- '<a href="http://127.0.0.1/+test" rel="external">127.0.0.1/+test</a>'),
+ '<a href="http://127.0.0.1/+test" title="http://127.0.0.1/+test" rel="external">127.0.0.1/+test</a>'),
array('127.0.0.1/$test',
- '<a href="http://127.0.0.1/$test" rel="external">127.0.0.1/$test</a>'),
+ '<a href="http://127.0.0.1/$test" title="http://127.0.0.1/$test" rel="external">127.0.0.1/$test</a>'),
array('127.0.0.1/\'test',
- '<a href="http://127.0.0.1/\'test" rel="external">127.0.0.1/\'test</a>'),
+ '<a href="http://127.0.0.1/\'test" title="http://127.0.0.1/\'test" rel="external">127.0.0.1/\'test</a>'),
array('127.0.0.1/"test',
- '<a href="http://127.0.0.1/&quot;test" rel="external">127.0.0.1/&quot;test</a>'),
+ '<a href="http://127.0.0.1/&quot;test" title="http://127.0.0.1/&quot;test" rel="external">127.0.0.1/&quot;test</a>'),
array('127.0.0.1/-test',
- '<a href="http://127.0.0.1/-test" rel="external">127.0.0.1/-test</a>'),
+ '<a href="http://127.0.0.1/-test" title="http://127.0.0.1/-test" rel="external">127.0.0.1/-test</a>'),
array('127.0.0.1/_test',
- '<a href="http://127.0.0.1/_test" rel="external">127.0.0.1/_test</a>'),
+ '<a href="http://127.0.0.1/_test" title="http://127.0.0.1/_test" rel="external">127.0.0.1/_test</a>'),
array('127.0.0.1/!test',
- '<a href="http://127.0.0.1/!test" rel="external">127.0.0.1/!test</a>'),
+ '<a href="http://127.0.0.1/!test" title="http://127.0.0.1/!test" rel="external">127.0.0.1/!test</a>'),
array('127.0.0.1/*test',
- '<a href="http://127.0.0.1/*test" rel="external">127.0.0.1/*test</a>'),
+ '<a href="http://127.0.0.1/*test" title="http://127.0.0.1/*test" rel="external">127.0.0.1/*test</a>'),
array('127.0.0.1/test%20stuff',
- '<a href="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'),
+ '<a href="http://127.0.0.1/test%20stuff" title="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'),
array('http://[::1]:99/test.php',
- '<a href="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'),
+ '<a href="http://[::1]:99/test.php" title="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'),
array('http://::1/test.php',
- '<a href="http://::1/test.php" rel="external">http://::1/test.php</a>'),
+ '<a href="http://::1/test.php" title="http://::1/test.php" rel="external">http://::1/test.php</a>'),
array('http://::1',
- '<a href="http://::1/" rel="external">http://::1</a>'),
+ '<a href="http://::1/" title="http://::1/" rel="external">http://::1</a>'),
array('2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php',
- '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'),
+ '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'),
array('[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php',
- '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'),
+ '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" title="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'),
array('2001:4978:1b5:0:21d:e0ff:fe66:59ab',
- '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'),
+ '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'),
array('http://127.0.0.1',
- '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
+ '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
array('example.com',
- '<a href="http://example.com/" rel="external">example.com</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
array('example.com',
- '<a href="http://example.com/" rel="external">example.com</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
array('http://example.com',
- '<a href="http://example.com/" rel="external">http://example.com</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'),
array('http://example.com.',
- '<a href="http://example.com/" rel="external">http://example.com</a>.'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'),
array('/var/lib/example.so',
'/var/lib/example.so'),
array('example',
'example'),
array('user@example.com',
- '<a href="mailto:user@example.com" rel="external">user@example.com</a>'),
+ '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">user@example.com</a>'),
array('user_name+other@example.com',
- '<a href="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'),
+ '<a href="mailto:user_name+other@example.com" title="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'),
array('mailto:user@example.com',
- '<a href="mailto:user@example.com" rel="external">mailto:user@example.com</a>'),
+ '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">mailto:user@example.com</a>'),
array('mailto:user@example.com?subject=test',
- '<a href="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'),
+ '<a href="mailto:user@example.com?subject=test" title="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'),
+ array('xmpp:user@example.com',
+ '<a href="xmpp:user@example.com" title="xmpp:user@example.com" rel="external">xmpp:user@example.com</a>'),
array('#example',
'#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('example'))) . '" rel="tag">example</a></span>'),
array('#example.com',
@@ -96,165 +101,165 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
array('#.net',
'#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('.net'))) . '" rel="tag">.net</a></span>'),
array('http://example',
- '<a href="http://example/" rel="external">http://example</a>'),
+ '<a href="http://example/" title="http://example/" rel="external">http://example</a>'),
array('http://3xampl3',
- '<a href="http://3xampl3/" rel="external">http://3xampl3</a>'),
+ '<a href="http://3xampl3/" title="http://3xampl3/" rel="external">http://3xampl3</a>'),
array('http://example/',
- '<a href="http://example/" rel="external">http://example/</a>'),
+ '<a href="http://example/" title="http://example/" rel="external">http://example/</a>'),
array('http://example/path',
- '<a href="http://example/path" rel="external">http://example/path</a>'),
+ '<a href="http://example/path" title="http://example/path" rel="external">http://example/path</a>'),
array('http://example.com',
- '<a href="http://example.com/" rel="external">http://example.com</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'),
array('https://example.com',
- '<a href="https://example.com/" rel="external">https://example.com</a>'),
+ '<a href="https://example.com/" title="https://example.com/" rel="external">https://example.com</a>'),
array('ftp://example.com',
- '<a href="ftp://example.com/" rel="external">ftp://example.com</a>'),
+ '<a href="ftp://example.com/" title="ftp://example.com/" rel="external">ftp://example.com</a>'),
array('ftps://example.com',
- '<a href="ftps://example.com/" rel="external">ftps://example.com</a>'),
+ '<a href="ftps://example.com/" title="ftps://example.com/" rel="external">ftps://example.com</a>'),
array('http://user@example.com',
- '<a href="http://user@example.com/" rel="external">http://user@example.com</a>'),
+ '<a href="http://user@example.com/" title="http://user@example.com/" rel="external">http://user@example.com</a>'),
array('http://user:pass@example.com',
- '<a href="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'),
+ '<a href="http://user:pass@example.com/" title="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'),
array('http://example.com:8080',
- '<a href="http://example.com:8080/" rel="external">http://example.com:8080</a>'),
+ '<a href="http://example.com:8080/" title="http://example.com:8080/" rel="external">http://example.com:8080</a>'),
array('http://example.com:8080/test.php',
- '<a href="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'),
+ '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'),
array('example.com:8080/test.php',
- '<a href="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'),
+ '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'),
array('http://www.example.com',
- '<a href="http://www.example.com/" rel="external">http://www.example.com</a>'),
+ '<a href="http://www.example.com/" title="http://www.example.com/" rel="external">http://www.example.com</a>'),
array('http://example.com/',
- '<a href="http://example.com/" rel="external">http://example.com/</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com/</a>'),
array('http://example.com/path',
- '<a href="http://example.com/path" rel="external">http://example.com/path</a>'),
+ '<a href="http://example.com/path" title="http://example.com/path" rel="external">http://example.com/path</a>'),
array('http://example.com/path.html',
- '<a href="http://example.com/path.html" rel="external">http://example.com/path.html</a>'),
+ '<a href="http://example.com/path.html" title="http://example.com/path.html" rel="external">http://example.com/path.html</a>'),
array('http://example.com/path.html#fragment',
- '<a href="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'),
+ '<a href="http://example.com/path.html#fragment" title="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'),
array('http://example.com/path.php?foo=bar&bar=foo',
- '<a href="http://example.com/path.php?foo=bar&amp;bar=foo" rel="external">http://example.com/path.php?foo=bar&amp;bar=foo</a>'),
+ '<a href="http://example.com/path.php?foo=bar&amp;bar=foo" title="http://example.com/path.php?foo=bar&amp;bar=foo" rel="external">http://example.com/path.php?foo=bar&amp;bar=foo</a>'),
array('http://example.com.',
- '<a href="http://example.com/" rel="external">http://example.com</a>.'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'),
array('http://müllärör.de',
- '<a href="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" rel="external">http://müllärör.de</a>'),
+ '<a href="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" title="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" rel="external">http://müllärör.de</a>'),
array('http://ﺱﺲﺷ.com',
- '<a href="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" rel="external">http://ﺱﺲﺷ.com</a>'),
+ '<a href="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" title="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" rel="external">http://ﺱﺲﺷ.com</a>'),
array('http://сделаткартинки.com',
- '<a href="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" rel="external">http://сделаткартинки.com</a>'),
+ '<a href="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" title="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" rel="external">http://сделаткартинки.com</a>'),
array('http://tūdaliņ.lv',
- '<a href="http://t&#x16B;dali&#x146;.lv/" rel="external">http://tūdaliņ.lv</a>'),
+ '<a href="http://t&#x16B;dali&#x146;.lv/" title="http://t&#x16B;dali&#x146;.lv/" rel="external">http://tūdaliņ.lv</a>'),
array('http://brændendekærlighed.com',
- '<a href="http://br&#xE6;ndendek&#xE6;rlighed.com/" rel="external">http://brændendekærlighed.com</a>'),
+ '<a href="http://br&#xE6;ndendek&#xE6;rlighed.com/" title="http://br&#xE6;ndendek&#xE6;rlighed.com/" rel="external">http://brændendekærlighed.com</a>'),
array('http://あーるいん.com',
- '<a href="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" rel="external">http://あーるいん.com</a>'),
+ '<a href="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" title="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" rel="external">http://あーるいん.com</a>'),
array('http://예비교사.com',
- '<a href="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" rel="external">http://예비교사.com</a>'),
+ '<a href="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" title="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" rel="external">http://예비교사.com</a>'),
array('http://example.com.',
- '<a href="http://example.com/" rel="external">http://example.com</a>.'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'),
array('http://example.com?',
- '<a href="http://example.com/" rel="external">http://example.com</a>?'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>?'),
array('http://example.com!',
- '<a href="http://example.com/" rel="external">http://example.com</a>!'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>!'),
array('http://example.com,',
- '<a href="http://example.com/" rel="external">http://example.com</a>,'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>,'),
array('http://example.com;',
- '<a href="http://example.com/" rel="external">http://example.com</a>;'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>;'),
array('http://example.com:',
- '<a href="http://example.com/" rel="external">http://example.com</a>:'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>:'),
array('\'http://example.com\'',
- '\'<a href="http://example.com/" rel="external">http://example.com</a>\''),
+ '\'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>\''),
array('"http://example.com"',
- '&quot;<a href="http://example.com/" rel="external">http://example.com</a>&quot;'),
+ '&quot;<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>&quot;'),
array('http://example.com',
- '<a href="http://example.com/" rel="external">http://example.com</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'),
array('(http://example.com)',
- '(<a href="http://example.com/" rel="external">http://example.com</a>)'),
+ '(<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>)'),
array('[http://example.com]',
- '[<a href="http://example.com/" rel="external">http://example.com</a>]'),
+ '[<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>]'),
array('<http://example.com>',
- '&lt;<a href="http://example.com/" rel="external">http://example.com</a>&gt;'),
+ '&lt;<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>&gt;'),
array('http://example.com/path/(foo)/bar',
- '<a href="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'),
+ '<a href="http://example.com/path/(foo)/bar" title="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'),
array('http://example.com/path/[foo]/bar',
- '<a href="http://example.com/path/[foo]/bar" rel="external">http://example.com/path/[foo]/bar</a>'),
+ '<a href="http://example.com/path/[foo]/bar" title="http://example.com/path/[foo]/bar" rel="external">http://example.com/path/[foo]/bar</a>'),
array('http://example.com/path/foo/(bar)',
- '<a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>'),
+ '<a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>'),
//Not a valid url - urls cannot contain unencoded square brackets
array('http://example.com/path/foo/[bar]',
- '<a href="http://example.com/path/foo/[bar]" rel="external">http://example.com/path/foo/[bar]</a>'),
+ '<a href="http://example.com/path/foo/[bar]" title="http://example.com/path/foo/[bar]" rel="external">http://example.com/path/foo/[bar]</a>'),
array('Hey, check out my cool site http://example.com okay?',
- 'Hey, check out my cool site <a href="http://example.com/" rel="external">http://example.com</a> okay?'),
+ 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a> okay?'),
array('What about parens (e.g. http://example.com/path/foo/(bar))?',
- 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)?'),
+ 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)?'),
array('What about parens (e.g. http://example.com/path/foo/(bar)?',
- 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>?'),
+ 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>?'),
array('What about parens (e.g. http://example.com/path/foo/(bar).)?',
- 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>.)?'),
+ 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>.)?'),
//Not a valid url - urls cannot contain unencoded commas
array('What about parens (e.g. http://example.com/path/(foo,bar)?',
- 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">http://example.com/path/(foo,bar)</a>?'),
+ 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="external">http://example.com/path/(foo,bar)</a>?'),
array('Unbalanced too (e.g. http://example.com/path/((((foo)/bar)?',
- 'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" rel="external">http://example.com/path/((((foo)/bar)</a>?'),
+ 'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" title="http://example.com/path/((((foo)/bar)" rel="external">http://example.com/path/((((foo)/bar)</a>?'),
array('Unbalanced too (e.g. http://example.com/path/(foo))))/bar)?',
- 'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" rel="external">http://example.com/path/(foo))))/bar</a>)?'),
+ 'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" title="http://example.com/path/(foo))))/bar" rel="external">http://example.com/path/(foo))))/bar</a>)?'),
array('Unbalanced too (e.g. http://example.com/path/foo/((((bar)?',
- 'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" rel="external">http://example.com/path/foo/((((bar)</a>?'),
+ 'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" title="http://example.com/path/foo/((((bar)" rel="external">http://example.com/path/foo/((((bar)</a>?'),
array('Unbalanced too (e.g. http://example.com/path/foo/(bar))))?',
- 'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)))?'),
+ 'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)))?'),
array('example.com',
- '<a href="http://example.com/" rel="external">example.com</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
array('example.org',
- '<a href="http://example.org/" rel="external">example.org</a>'),
+ '<a href="http://example.org/" title="http://example.org/" rel="external">example.org</a>'),
array('example.co.uk',
- '<a href="http://example.co.uk/" rel="external">example.co.uk</a>'),
+ '<a href="http://example.co.uk/" title="http://example.co.uk/" rel="external">example.co.uk</a>'),
array('www.example.co.uk',
- '<a href="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'),
+ '<a href="http://www.example.co.uk/" title="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'),
array('farm1.images.example.co.uk',
- '<a href="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'),
+ '<a href="http://farm1.images.example.co.uk/" title="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'),
array('example.museum',
- '<a href="http://example.museum/" rel="external">example.museum</a>'),
+ '<a href="http://example.museum/" title="http://example.museum/" rel="external">example.museum</a>'),
array('example.travel',
- '<a href="http://example.travel/" rel="external">example.travel</a>'),
+ '<a href="http://example.travel/" title="http://example.travel/" rel="external">example.travel</a>'),
array('example.com.',
- '<a href="http://example.com/" rel="external">example.com</a>.'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.'),
array('example.com?',
- '<a href="http://example.com/" rel="external">example.com</a>?'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>?'),
array('example.com!',
- '<a href="http://example.com/" rel="external">example.com</a>!'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>!'),
array('example.com,',
- '<a href="http://example.com/" rel="external">example.com</a>,'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>,'),
array('example.com;',
- '<a href="http://example.com/" rel="external">example.com</a>;'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>;'),
array('example.com:',
- '<a href="http://example.com/" rel="external">example.com</a>:'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>:'),
array('\'example.com\'',
- '\'<a href="http://example.com/" rel="external">example.com</a>\''),
+ '\'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>\''),
array('"example.com"',
- '&quot;<a href="http://example.com/" rel="external">example.com</a>&quot;'),
+ '&quot;<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>&quot;'),
array('example.com',
- '<a href="http://example.com/" rel="external">example.com</a>'),
+ '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
array('(example.com)',
- '(<a href="http://example.com/" rel="external">example.com</a>)'),
+ '(<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>)'),
array('[example.com]',
- '[<a href="http://example.com/" rel="external">example.com</a>]'),
+ '[<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>]'),
array('<example.com>',
- '&lt;<a href="http://example.com/" rel="external">example.com</a>&gt;'),
+ '&lt;<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>&gt;'),
array('Hey, check out my cool site example.com okay?',
- 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a> okay?'),
+ 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a> okay?'),
array('Hey, check out my cool site example.com.I made it.',
- 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.I made it.'),
+ 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.I made it.'),
array('Hey, check out my cool site example.com.Funny thing...',
- 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.Funny thing...'),
+ 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.Funny thing...'),
array('Hey, check out my cool site example.com.You will love it.',
- 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.You will love it.'),
+ 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.You will love it.'),
array('What about parens (e.g. example.com/path/foo/(bar))?',
- 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'),
+ 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'),
array('What about parens (e.g. example.com/path/foo/(bar)?',
- 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'),
+ 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'),
array('What about parens (e.g. example.com/path/foo/(bar).)?',
- 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'),
+ 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'),
array('What about parens (e.g. example.com/path/(foo,bar)?',
- 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'),
+ 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'),
array('file.ext',
'file.ext'),
array('file.html',
diff --git a/tests/UserRightsTest.php b/tests/UserRightsTest.php
new file mode 100644
index 000000000..6544ee53d
--- /dev/null
+++ b/tests/UserRightsTest.php
@@ -0,0 +1,59 @@
+<?php
+
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('STATUSNET', true);
+
+require_once INSTALLDIR . '/lib/common.php';
+
+class UserRightsTest extends PHPUnit_Framework_TestCase
+{
+ protected $user = null;
+
+ function setUp()
+ {
+ $this->user = User::register(array('nickname' => 'userrightstestuser'));
+ }
+
+ function tearDown()
+ {
+ $profile = $this->user->getProfile();
+ $this->user->delete();
+ $profile->delete();
+ }
+
+ function testInvalidRole()
+ {
+ $this->assertFalse($this->user->hasRole('invalidrole'));
+ }
+
+ function standardRoles()
+ {
+ return array('admin', 'moderator');
+ }
+
+ /**
+ * @dataProvider standardRoles
+ *
+ */
+
+ function testUngrantedRole($role)
+ {
+ $this->assertFalse($this->user->hasRole($role));
+ }
+
+ /**
+ * @dataProvider standardRoles
+ *
+ */
+
+ function testGrantedRole($role)
+ {
+ $this->user->grantRole($role);
+ $this->assertFalse($this->user->hasRole($role));
+ }
+} \ No newline at end of file
diff --git a/theme/base/css/display.css b/theme/base/css/display.css
index 1f37a7637..d9dca9815 100644
--- a/theme/base/css/display.css
+++ b/theme/base/css/display.css
@@ -251,7 +251,7 @@ margin-right:18px;
margin-bottom:11px;
margin-left:18px;
}
-#site_nav_global_primary ul li {
+#site_nav_global_primary li {
display:inline;
margin-left:11px;
}
@@ -468,23 +468,22 @@ margin-bottom:7px;
#form_notice #notice_data-attach {
position:absolute;
top:25px;
+right:10.5%;
cursor:pointer;
}
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
-left:86%;
width:16px;
height:16px;
}
#form_notice #notice_data-attach {
-left:40.6%;
padding:0;
height:16px;
}
#form_notice .form_note {
position:absolute;
bottom:2px;
-right:98px;
+right:21.715%;
z-index:9;
}
#form_notice .form_note dt {
diff --git a/theme/base/images/icons/icons-01.png b/theme/base/images/icons/icons-01.png
new file mode 100644
index 000000000..c4e371330
--- /dev/null
+++ b/theme/base/images/icons/icons-01.png
Binary files differ
diff --git a/theme/cloudy/css/display.css b/theme/cloudy/css/display.css
index 3851bc057..3fe05eb3d 100644
--- a/theme/cloudy/css/display.css
+++ b/theme/cloudy/css/display.css
@@ -120,6 +120,10 @@ float:left;
margin-left:11px;
float:left;
}
+.form_settings .form_data textarea {
+width:325px;
+}
+
.form_settings .form_data input.submit {
margin-left:0;
}
@@ -968,9 +972,6 @@ right:7px;
top:47px;
right:7px;
}
-.notice-options .notice_reply dt {
-display:none;
-}
.notice-options input,
.notice-options a {
@@ -978,13 +979,13 @@ text-indent:-9999px;
outline:none;
}
-.notice-options .notice_reply a,
+.notice-options .notice_reply,
.notice-options input.submit {
display:block;
border:0;
}
-.notice-options .notice_reply a,
-.notice-options .notice_delete a {
+.notice-options .notice_reply,
+.notice-options .notice_delete {
text-decoration:none;
padding-left:16px;
}
@@ -1375,6 +1376,12 @@ padding-top:160px;
#smssettings #form_notice,
#twittersettings #form_notice,
#imsettings #form_notice,
+#userdesignsettings #form_notice,
+#groupdesignsettings #form_notice,
+#grouplogo #form_notice,
+#editgroup #form_notice,
+#blockedfromgroup #form_notice,
+#groupmembers #form_notice,
#doc #form_notice,
#usergroups #form_notice,
#invite #form_notice,
@@ -1584,11 +1591,11 @@ background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no
background:none;
}
-.notice-options .notice_reply a,
+.notice-options .notice_reply,
.notice-options form input.submit {
background-color:transparent;
}
-.notice-options .notice_reply a {
+.notice-options .notice_reply {
background:transparent url(../images/icons/icon_reply.gif) no-repeat 0 45%;
}
.notice-options form.form_favor input.submit {
@@ -1597,7 +1604,7 @@ background:transparent url(../images/icons/icon_favourite.gif) no-repeat 0 45%;
.notice-options form.form_disfavor input.submit {
background:transparent url(../images/icons/icon_disfavourite.gif) no-repeat 0 45%;
}
-.notice-options .notice_delete a {
+.notice-options .notice_delete {
background:transparent url(../images/icons/icon_trash.gif) no-repeat 0 45%;
}
diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css
index 9fc97180d..6339c9314 100644
--- a/theme/identica/css/display.css
+++ b/theme/identica/css/display.css
@@ -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:#CEE1E9;
}
.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;
}
diff --git a/theme/identica/css/ie.css b/theme/identica/css/ie.css
index 97cabc30a..044c32ff1 100644
--- a/theme/identica/css/ie.css
+++ b/theme/identica/css/ie.css
@@ -7,8 +7,14 @@ color:#FFFFFF;
background-color:#D9DADB;
}
#form_notice .form_note + label {
-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 {
filter: alpha(opacity=0);
}
+.notice-options form.form_favor input.submit {
+background-position:0 -460px;
+}
+.notice-options form.form_disfavor input.submit {
+background-position:0 -526px;
+}
diff --git a/theme/iphone/bg-body.gif b/theme/iphone/bg-body.gif
deleted file mode 100644
index d87e2e8d7..000000000
--- a/theme/iphone/bg-body.gif
+++ /dev/null
Binary files differ
diff --git a/theme/iphone/bg-header.gif b/theme/iphone/bg-header.gif
deleted file mode 100644
index 5154b2e5e..000000000
--- a/theme/iphone/bg-header.gif
+++ /dev/null
Binary files differ
diff --git a/theme/iphone/default-avatar-mini.png b/theme/iphone/default-avatar-mini.png
deleted file mode 100644
index 38b8692b4..000000000
--- a/theme/iphone/default-avatar-mini.png
+++ /dev/null
Binary files differ
diff --git a/theme/iphone/default-avatar-profile.png b/theme/iphone/default-avatar-profile.png
deleted file mode 100644
index f8357d4fc..000000000
--- a/theme/iphone/default-avatar-profile.png
+++ /dev/null
Binary files differ
diff --git a/theme/iphone/default-avatar-stream.png b/theme/iphone/default-avatar-stream.png
deleted file mode 100644
index 6b63baa70..000000000
--- a/theme/iphone/default-avatar-stream.png
+++ /dev/null
Binary files differ
diff --git a/theme/iphone/display.css b/theme/iphone/display.css
deleted file mode 100644
index 1838a8e86..000000000
--- a/theme/iphone/display.css
+++ /dev/null
@@ -1,698 +0,0 @@
-/* CSS Document */
-/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
-/* Simplified for mobile by Ken Sheppardson http://identi.ca/kshep */
-
-@import url(../../base/css/display.css);
-
-html {}
-body {
- width: 100%;
- padding: 0;
- margin: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 24px;
- min-height: 100%;
- height: 100%;
- color: #193441;
-}
-
-a {
- color: #C15D42;
- text-decoration: none;
-}
-a:hover {
- text-decoration: underline;
-}
-img, img a {
- border: 0;
-}
-h1 {
- font-size: 1.2em;
-}
-
-#wrap {
- margin: 0;
-}
-
-#header {
- width: 100%;
- float: left;
- background-color: #193441;
- margin: 0 0 20px 0;
- padding: 0;
-}
-#logo {
- float: left;
- margin: 10px 0px 0px 10px;
-}
-p#branding {
- margin: 0;
- padding: 6px 0 3px 0;
- color: #fbf2d7;
- font-size: 2em;
- font-weight: bold;
- line-height: 2.5em;
-}
-p#branding a {
- color: #dab134;
-}
-
-#header h1.pagetitle {
- display: none;
- margin: 0;
- padding: 0;
- font-size: 1.2em;
- line-height: 2em;
- color: #d8e2d7;
-}
-
-#header h2.sitename {
- display: none;
- margin: 0;
- padding: 0;
- color: #FCFFF5;
-}
-
-/* ===== Begin Navigation Styling ===== */
-
-/* ----- Navigation ------ */
-#nav {
- float: right;
- margin: 0;
- padding: 0;
- list-style-type: none;
- font-size: 1.2em;
-}
-#nav li {
- display: block;
- float: left;
-}
-#nav li a {
- display: block;
- padding: 9px 15px 12px 0px;
- color: #91AA9D;
-}
-#nav li a:hover {
- text-decoration: underline;
-}
-
-/* ----- Tabs ----- */
-#nav_views {
- clear: both;
- float: left;
- margin: 10px 0px 0px 5px;
- padding: 0;
- bottom: 0;
- list-style-type: none;
- font-size: 1.1em;
- font-weight: bold;
-}
-#nav_views li {
- display: block;
- float: left;
- line-height: 1.3em;
-}
-#nav_views li a {
- display: block;
- margin: 0;
- padding: 4px 12px 3px 12px;
- color: #FCFFF5;
- background-color: #91AA9D;
- border-right: 1px solid #6A8787;
-}
-#nav_views li a:hover {
- text-decoration: none;
-}
-#nav_views li.current a, #nav_views li.current a:hover {
- color: #3F606F;
- background-color: #FCFFF5;
- border-right: 1px solid #6A8787;
-}
-#nav_views li.current a:hover {
- color: #193441;
-}
-#nav_views li a:hover {
- color: #FCFFF5;
- background-color: #3F606F;
- border-right: 1px solid #6A8787;
-}
-
-/* ----- Nav Footer ----- */
-#nav_sub {
- clear: both;
- margin: 18px 10px 0 10px;
- padding: 0;
- list-style-type: none;
- font-size: 1.1em;
- font-weight: bold;
- line-height: 2em;
- border-top: 1px solid #D8E2D7;
-}
-#nav_sub li {
- display: block;
- float: left;
-}
-#nav_sub li a {
- padding: 6px 24px 6px 0;
-}
-#nav_sub li a:hover {
- text-decoration: underline;
-}
-/* ===== End Navigation Styling ===== */
-
-#content {
- clear: left;
- margin: 10px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 1em;
- line-height: 1.1em;
-}
-#content h2 {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 1.1em;
-}
-#content label {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 1.1em;
-}
-
-.instructions {
- clear: both;
- float: left;
- margin: 5px 5px 10px 5px;
-}
-.instructions p, .success, .error {
- font-weight: normal;
- margin: 0;
- padding: 10px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 1.1em;
- line-height: 1.2em;
- border: 1px solid #91AA9D;
- color: #FCFFF5;
-}
-.instructions a, .success a, .error a {
- color: #d8e2d7;
- text-decoration: underline;
-}
-.instructions a:hover, .success a:hover, .error a:hover {
- color: #FCFFF5;
-}
-.success {
- clear: both;
- float: left;
- margin: 5px 5px 10px 5px;
- background-color: #48705b;
-}
-.error {
- clear: both;
- float: left;
- margin: 5px 5px 10px 5px;
- background-color: #ce3728;
-}
-
-/* ----- Stream -----*/
-
-#notices {
- clear: both;
- margin: 0 auto;
- padding: 0;
- list-style-type: none;
- border-top: 1px solid #D8E2D7;
-}
-#notices a:hover {
- text-decoration: underline;
-}
-.notice_single {
- clear: both;
- display: block;
- margin: 0;
- padding: 5px 5px 5px 0;
- min-height: 48px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 1em;
- line-height: 1.4em;
- border-bottom: 1px solid #D8E2D7;
-}
-.notice_single:hover {
- background-color: #F3F8EA;
-}
-.notice_single p {
- display: inline;
- margin: 0;
- padding: 0;
-}
-#notice_delete_form #confirmation_text {
- display: block;
- font-size: 1.1em;
- font-weight: bold;
-}
-input#submit_yes, input#submit_no {
- margin: 18px 10px 0px 0px;
- padding: 4px;
- font-weight: bold;
- color: #FCFFF5;
- background-color: #C15D42;
- cursor: pointer;
- border: 0;
- width: 40px;
-}
-input#submit_yes:hover, input#submit_no:hover {
- background-color: #904632;
-}
-.avatar.stream {
- float: left;
- margin: 0 10px 0 0;
-}
-p.time {
- display: block;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 0.9em;
- line-height: 2em;
-}
-p.time a {
- color: #91AA9D;
-}
-
-/* ----- Profile -----*/
-#profile {
- clear: both;
- float: left;
- padding: 10px 0 0 0;
- border-top: 1px solid #D8E2D7;
- font-family: Georgia, "Times New Roman", Times, serif;
-}
-#profile h1 {
- clear: both;
- float: left;
- margin: 0;
- padding: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 1.2em;
-}
-#profile h2 {
- clear: both;
- float: left;
- margin: 0;
- padding: 1em 0 0.2em 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 1.1em;
- text-transform: uppercase;
- color: #91AA9D;
-}
-#profile p {
- clear: both;
- float: left;
- margin: 0 10px 0 0;
- font-size: 1em;
- line-height: 1.4em;
-}
-#profile p.location {
- margin: 0 10px 12px 0;
- font-style: italic;
-}
-#profile p.notice_current {
- font-size: 1.2em;
- line-height: 1.3em;
-}
-#profile_avatar {
- float: left;
- margin-right: 4px;
-}
-#profile_avatar img {
- margin-bottom: 5px;
-}
-.avatar.profile {
- clear: left;
- margin: 0 10px 5px 0;
-}
-.avatar.original {
- float: left;
- margin: 0 10px 18px 0;
-}
-a.nickname {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-weight: bold;
- font-size: 1.1em;
- padding-right: 3px;
-}
-#profile_information {
- float: left;
-}
-
-.statistics {
- clear: both;
- float: left;
-}
-.statistics h2 {
- clear: both;
- float: left;
- margin: 12px 0 3px 0;
-}
-dl.statistics {
- margin: 0;
-}
-.statistics dt {
- clear: left;
- float: left;
- width: 200px;
-}
-.statistics dd {
- float: left;
-}
-.statistics dt:after {
- content: ":";
-}
-#subscriptions {
- clear: both;
- float: left;
- margin: 18px 0 30px 0;
-}
-#subscriptions_avatars {
- clear: both;
- float: left;
- margin: 6px 0 0 0;
- padding: 0;
- list-style-type: none;
-}
-#subscriptions_avatars li .avatar.mini {
- float: left;
- margin: 0 3px 3px 0;
- padding: 0;
- line-height: 0;
-}
-#subscriptions_viewall {
- clear: left;
-}
-/* ----- End Profile -----*/
-
-/* ----- Begin Subscriptions & Subscribers -----*/
-
-ul.subscriptions, ul.subscribers {
- float: none;
- margin: 0;
- padding: 0;
- list-style-type: none;
- overflow: auto;
-}
-ul.subscriptions li, ul.subscribers li {
- display: block;
- float: left;
- padding: 0;
-}
-/* ----- End Subscriptions & Subscribers -----*/
-
-#pagination {
- margin: 18px auto;
-}
-#nav_pagination {
- margin: 0 0 36px 0;
- padding: 0;
- float: right;
- list-style-type: none;
- font-size: 12px;
- font-weight: bold;
-}
-#nav_pagination li {
- display: block;
- float: left;
- background-color: #91AA9D;
-}
-#nav_pagination li.before {
- margin-right: 1px;
-}
-#nav_pagination li a {
- padding: 6px 15px;
- line-height: 2em;
- background-color: #91AA9D;
- color: #FCFFF5;
-}
-#nav_pagination li a:hover {
- background-color: #3F606F;
- color: #FCFFF5;
- text-decoration: none;
-}
-
-#footer {
- clear: both;
- margin: 10px;
- border-top: 1px solid #D8E2D7;
-}
-#footer p {
- font-size: 0.8em;
- margin-top: 1em;
- line-height: 1.2em;
-}
-#cc {
- float: left;
- margin: 3px 10px 0 0;
-}
-
-/* ===== Begin Forms Styling ===== */
-
-/* ----- Forms General Style ----- */
-form {
- margin: 0 auto;
- padding: 0;
-}
-form {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 1em;
-}
-form label {
- display: block;
- font-size: 1em;
- font-weight: bold;
- line-height: 1.5em;
-}
-form input {
- border: 1px solid #D8E2D7;
- width: 264px;
-}
-input#submit, input.submit {
- display: block;
- margin: 18px 0;
- padding: 4px;
- font-weight: bold;
- color: #FCFFF5;
- background-color: #C15D42;
- cursor: pointer;
- border: 0;
- width: auto;
-}
-input#submit:hover, input.submit:hover {
- background-color: #904632;
-}
-input.checkbox {
- width: auto;
- border: 0;
-}
-textarea, input {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 1em;
- color: #193441;
- padding: 3px;
-}
-textarea:focus, input:focus {
- background-color: #f0f6eb;
-}
-textarea {
- width: 270px;
- border: 1px solid #D8E2D7;
-}
-.input_instructions {
- margin-top: 3px;
- display: block;
- font-size: 1em;
- line-height: 1.2em;
- color: #91aa9d;
- font-family: Verdana, Arial, Helvetica, sans-serif;
-}
-
-/* ----- Status Form ----- */
-#status_form {
- width: 100%;
- margin: 0px 0px 10px 5px;
-}
-#status_form p {
- margin: 0;
- padding: 0;
-}
-#status_label {
- display: none;
- clear: both;
- margin: 0;
- padding: 0 0 3px 0;
- font-size: 1.5em;
- font-weight: bold;
- line-height: 2em;
- color: #91AA9D;
-}
-#status_textarea {
- display: block;
- float: left;
- width: 70%;
- height: 3em;
- margin: 0 0 10px 0;
- padding: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 1.1em;
- color: #193441;
- border: 0;
-}
-#status_submit {
- display: block;
- float: left;
- margin: 0 0 0 4px;
- padding: 1em 10px 1em 10px;
- line-height: 1em;
- width: 10%;
- background-color: #C15D42;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-weight: bold;
- font-size: 1em;
- color: #FCFFF5;
- cursor: pointer;
- border: 0;
-}
-#status_submit:hover {
- background-color: #904632;
-}
-#counter {
- padding: 1em .5em 1em 5px;
- color: #fff;
- clear: both;
- float: left;
- font-weight: bold;
- text-align: right;
-}
-/* ----- Subscribe Form ----- */
-#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
- clear: left;
- margin: 0;
- width: 96px;
- height: 27px;
- font-family: verdana, arial, helvetica, sans-serif;
- font-weight: bold;
- font-size: 12px;
- text-transform: uppercase;
- background-color: #c15d42;
- color: #fcfff5;
- border: 0;
-}
-#remotesubscribe {
- width: 96px;
- height: 22px;
- padding: 5px 0 0 0;
- text-align: center;
-}
-#subscribe .button:hover, #unsubscribe .button:hover {
- background-color: #904632;
- cursor: pointer;
-}
-
-a#remotesubscribe {
- display: block;
-}
-
-/* ----- Login Form -----*/
-input#license {
- width: auto;
- border: 0;
-}
-/* ----- Avatar Form -----*/
-form {
- clear: left;
-}
-
-/* ----- OpenID Form -----*/
-
-input#openid_url {
- background: url(login-bg.gif) no-repeat;
- background-color: #fff;
- background-position: 4px 50%;
- color: #000;
- padding-left: 24px;
-}
-
-/* People lists (search results, maybe subscribers) */
-
-#profiles {
- clear: both;
- margin: 0 auto;
- padding: 0;
- list-style-type: none;
- border-top: 1px solid #D8E2D7;
-}
-#profiles a:hover {
- text-decoration: underline;
-}
-
-.profile_single {
- clear: both;
- display: block;
- margin: 0;
- padding: 5px 5px 5px 0;
- min-height: 48px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 1.2em;
- line-height: 1.4em;
- border-bottom: 1px solid #D8E2D7;
-}
-.profile_single:hover {
- background-color: #F3F8EA;
-}
-
-/* ----- IM Settings Form -----*/
-
-#imsettings p {
- margin: 0;
- padding: 0;
- line-height: 1.3em;
-}
-
-/* ===== End Forms Styling ===== */
-
-/* ===== Tag Cloud Styling ===== */
-
-p.tagcloud {
-text-align: center;
-}
-
-p.tagcloud a {
-line-height:1em;
-vertical-align:middle;
-}
-
-p.tagcloud a.largest {
-font-size: 4em;
-}
-p.tagcloud a.verylarge {
-font-size: 3em;
-}
-
-p.tagcloud a.large {
-font-size: 2em;
-}
-
-p.tagcloud a.medium {
-font-size: 1.5em;
-}
-
-p.tagcloud a.small {
-font-size: 1em;
-}
-
-p.tagcloud a.verysmall {
-font-size: 80%;
-}
-
-p.tagcloud a.smallest {
-font-size: 60%;
-}
-
-a.replybutton {
- border: 1px solid #D8E2D7;
- padding: 0px 10px 0px 10px;
- line-height: 0.8em;
-}
diff --git a/theme/iphone/display.css.dist b/theme/iphone/display.css.dist
deleted file mode 100644
index 395da2e2f..000000000
--- a/theme/iphone/display.css.dist
+++ /dev/null
@@ -1,686 +0,0 @@
-/* CSS Document */
-/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
-
-html {
- background: url(bg-body.gif) repeat-y top center #d8e2d7;
- }
-body {
- position: absolute;
- width: 100%;
- margin: 0;
- padding: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10px;
- line-height: 12px;
- min-height: 100%;
- height: 100%;
- color: #193441;
- }
-a {
- color: #C15D42;
- text-decoration: none;
- }
-a:hover {
- text-decoration: underline;
- }
-img, img a {
- border: 0;
- }
-h1 {
- font-size: 14px;
- }
-
-
-#wrap {
- margin: 0 auto;
- padding: 0 20px;
- width: 760px;
- background: url(bg-header.gif) repeat-x #FCFFF5;
- }
-#header {
- position: relative;
- margin: 0 auto;
- width: 540px;
- height: 216px;
- }
-#logo {
- margin-top: 9px;
- }
-p#branding {
- margin: 0;
- padding: 6px 0 3px 0;
- color: #fbf2d7;
- font-size: 21px;
- font-weight: bold;
- line-height: 27px;
- }
-p#branding a {
- color: #dab134;
- }
-
-#header h1.pagetitle {
- margin: 0;
- padding: 0;
- font-size: 15px;
- line-height: 24px;
- color: #d8e2d7;
-}
-
-#header h2.sitename {
- display: none;
- margin: 0;
- padding: 0;
- color: #FCFFF5;
-}
-
-/* ===== Begin Navigation Styling ===== */
-
-/* ----- Navigation ------ */
-#nav {
- float: right;
- margin: 0;
- padding: 0;
- list-style-type: none;
- font-size: 12px;
- }
-#nav li {
- display: block;
- float: left;
- }
-#nav li a {
- display: block;
- padding: 9px 9px 12px 9px;
- color: #91AA9D;
- }
-#nav li a:hover {
- text-decoration: underline;
- }
-
-/* ----- Tabs ----- */
-#nav_views {
- margin: 0 auto;
- padding: 0;
- position: absolute;
- bottom: 0;
- list-style-type: none;
- font-size: 14px;
- font-weight: bold;
- width: 540px;
- /*height: 30px;*/
- }
-#nav_views li {
- display: block;
- float: left;
- line-height: 21px;
- }
-#nav_views li a {
- display: block;
- margin: 0;
- padding: 4px 12px 3px 12px;
- color: #FCFFF5;
- background-color: #91AA9D;
- border-right: 1px solid #6A8787;
- }
-#nav_views li a:hover {
- text-decoration: none;
- }
-#nav_views li.current a, #nav_views li.current a:hover {
- color: #3F606F;
- background-color: #FCFFF5;
- border-right: 1px solid #6A8787;
- }
-#nav_views li.current a:hover {
- color: #193441;
- }
-#nav_views li a:hover {
- color: #FCFFF5;
- background-color: #3F606F;
- border-right: 1px solid #6A8787;
- }
-
-/* ----- Nav Footer ----- */
-#nav_sub {
- clear: both;
- margin: 18px auto 0 auto;
- padding: 0;
- list-style-type: none;
- font-size: 11px;
- font-weight: bold;
- line-height: 21px;
- border-top: 1px solid #D8E2D7;
- width: 540px;
- }
-#nav_sub li {
- display: block;
- float: left;
- }
-#nav_sub li a {
- padding: 6px 24px 6px 0;
- }
-#nav_sub li a:hover {
- text-decoration: underline;
- }
-/* ===== End Navigation Styling ===== */
-
-#content {
- clear: left;
- margin: 40px 0 45px 0;
- padding: 0 110px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 14px;
- line-height: 18px;
- }
-#content h2 {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 15px;
- }
-#content label {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12px;
- }
-.instructions p, .success, .error {
- font-weight: normal;
- margin: 36px 0 0 0;
- padding: 10px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 13px;
- line-height: 15px;
- border: 1px solid #91AA9D;
- color: #FCFFF5;
- }
-.instructions a, .success a, .error a {
- color: #d8e2d7;
- text-decoration: underline;
- }
-.instructions a:hover, .success a:hover, .error a:hover {
- color: #FCFFF5;
- }
-.success {
- background-color: #48705b;
- }
-.error {
- background-color: #ce3728;
- }
-
-
-/* ----- Stream -----*/
-
-#notices {
- clear: both;
- margin: 0 auto;
- padding: 0;
- list-style-type: none;
- width: 540px;
- border-top: 1px solid #D8E2D7;
- }
-#notices a:hover {
- text-decoration: underline;
- }
-.notice_single {
- clear: both;
- display: block;
- margin: 0;
- padding: 5px 5px 5px 0;
- min-height: 48px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 13px;
- line-height: 16px;
- border-bottom: 1px solid #D8E2D7;
- }
-.notice_single:hover {
- background-color: #F3F8EA;
- }
-.notice_single p {
- display: inline;
- margin: 0;
- padding: 0;
- }
-#notice_delete_form #confirmation_text {
- display: block;
- font-size: 14px;
- font-weight: bold;
- }
-input#submit_yes, input#submit_no {
- margin: 18px 10px 0px 0px;
- padding: 4px;
- font-weight: bold;
- color: #FCFFF5;
- background-color: #C15D42;
- cursor: pointer;
- border: 0;
- width: 40px;
- }
-input#submit_yes:hover, input#submit_no:hover {
- background-color: #904632;
- }
-.avatar.stream {
- float: left;
- margin: 0 10px 0 0;
- }
-p.time {
- display: block;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 10px;
- line-height: 15px;
- }
-p.time a {
- color: #91AA9D;
- }
-
-/* ----- Profile -----*/
-#profile {
- clear: left;
- margin: 0 -110px;
- padding: 10px 0 0 0;
- min-height: 170px;
- border-top: 1px solid #D8E2D7;
- font-family: Georgia, "Times New Roman", Times, serif;
- }
-#profile h1 {
- margin: 0;
- padding: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 14px;
- }
-#profile h2 {
- margin: 0;
- padding: 0;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- text-transform: uppercase;
- color: #91AA9D;
- }
-#profile p {
- margin: 0 10px 0 0;
- font-size: 12px;
- line-height: 14px;
- }
-#profile p.location {
- margin: 0 10px 12px 0;
- font-style: italic;
- }
-#profile p.notice_current {
- font-size: 18px;
- line-height: 21px;
- }
-#profile_avatar {
- float: left;
- margin-right: 4px;
- }
-#profile_avatar img {
- margin-bottom: 5px;
- }
-.avatar.profile {
- clear: left;
- margin: 0 10px 5px 0;
- }
-.avatar.original {
- float: left;
- margin: 0 10px 18px 0;
- }
-a.nickname {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-weight: bold;
- font-size: 12px;
- padding-right: 3px;
- }
-#profile_information {
- float: left;
- position: relative;
- width: 270px;
- height: 200px;
- }
-.statistics {
- margin-top: 18px;
- }
-.statistics h2 {
- margin: 12px 0 3px 0;
- }
-dl.statistics {
- margin: 0;
- font-size: 12px;
- line-height: 14px;
- }
-.statistics dt {
- float: left;
- width: 96px;
-}
-.statistics dd {
- margin-left: 100px;
-}
-.statistics dt:after {
- content: ":";
- }
-#subscriptions {
- float: left;
- margin: 18px 0 30px 0;
- }
-#subscriptions_avatars {
- float: left;
- margin: 6px 0 0 0;
- padding: 0;
- list-style-type: none;
- width: 270px;
- }
-#subscriptions_avatars li .avatar.mini {
- float: left;
- margin: 0 3px 3px 0;
- padding: 0;
- line-height: 0;
- /* border: 1px solid #f00; */
- }
-#subscriptions_viewall {
- clear: left;
- }
-/* ----- End Profile -----*/
-
-/* ----- Begin Subscriptions & Subscribers -----*/
-
-ul.subscriptions, ul.subscribers {
- float: none;
- margin: 0;
- padding: 0;
- list-style-type: none;
- overflow: auto;
- }
-ul.subscriptions li, ul.subscribers li {
- display: block;
- float: left;
- padding: 0;
- }
-/* ----- End Subscriptions & Subscribers -----*/
-
-
-
-#pagination {
- margin: 18px auto;
- width: 540px;
- }
-#nav_pagination {
- margin: 0 0 36px 0;
- padding: 0;
- float: right;
- list-style-type: none;
- font-size: 12px;
- font-weight: bold;
- }
-#nav_pagination li {
- display: block;
- float: left;
- background-color: #91AA9D;
- }
-#nav_pagination li.before {
- margin-right: 1px;
- }
-#nav_pagination li a {
- padding: 6px 15px;
- line-height: 21px;
- background-color: #91AA9D;
- color: #FCFFF5;
- }
-#nav_pagination li a:hover {
- background-color: #3F606F;
- color: #FCFFF5;
- text-decoration: none;
- }
-
-#footer {
- clear: both;
- margin: 0 auto;
- padding: 0 0 36px 0;
- width: 540px;
- border-top: 1px solid #D8E2D7;
- }
-#footer p {
- margin-top: 9px;
- line-height: 12px;
- }
-#cc {
- float: left;
- margin: 3px 10px 0 0;
- }
-
-/* ===== Begin Forms Styling ===== */
-
-/* ----- Forms General Style ----- */
-form {
- margin: 0 auto;
- padding: 0;
- }
-form {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12px;
- }
-form label {
- display: block;
- font-size: 12px;
- font-weight: bold;
- line-height: 18px;
- }
-form input {
- border: 1px solid #D8E2D7;
- width: 264px;
- }
-input#submit, input.submit {
- display: block;
- margin: 18px 0;
- padding: 4px;
- font-weight: bold;
- color: #FCFFF5;
- background-color: #C15D42;
- cursor: pointer;
- border: 0;
- width: auto;
- }
-input#submit:hover, input.submit:hover {
- background-color: #904632;
- }
-input.checkbox {
- /*width: 14px;
- height: 14px;*/
- width: auto;
- border: 0;
- }
-textarea, input {
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12px;
- color: #193441;
- padding: 3px;
- }
-textarea:focus, input:focus {
- background-color: #f0f6eb;
- }
-textarea {
- width: 270px;
- border: 1px solid #D8E2D7;
- }
-.input_instructions {
- margin-top: 3px;
- display: block;
- font-size: 11px;
- line-height: 15px;
- color: #91aa9d;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- }
-
-/* ----- Status Form ----- */
-#status_form {
- height: 96px;
- /*background-color: #F00;*/
- }
-#status_form p {
- margin: 36px 0 0 0;
- padding: 0;
- }
-#status_label {
- display: block;
- clear: both;
- margin: 0;
- padding: 0 0 3px 0;
- font-size: 18px;
- font-weight: bold;
- line-height: 24px;
- color: #91AA9D;
- }
-#status_textarea {
- display: block;
- float: left;
- width: 463px;
- height: 35px;
- padding: 5px;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12px;
- color: #193441;
- border: 0;
- }
-#status_submit {
- display: block;
- float: left;
- margin: 1px 0 0 4px;
- width: 63px;
- height: 45px;
- background-color: #C15D42;
- font-family: Verdana, Arial, Helvetica, sans-serif;
- font-weight: bold;
- font-size: 14px;
- color: #FCFFF5;
- cursor: pointer;
- border: 0;
- }
-#status_submit:hover {
- background-color: #904632;
- }
-#counter {
- position: absolute;
- top: 140px;
- left: -64px;
- width: 50px;
- font-weight: bold;
- text-align: right;
-}
-/* ----- Subscribe Form ----- */
-#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
- clear: left;
- margin: 0;
- width: 96px;
- height: 27px;
- font-family: verdana, arial, helvetica, sans-serif;
- font-weight: bold;
- font-size: 10px;
- text-transform: uppercase;
- background-color: #c15d42;
- color: #fcfff5;
- border: 0;
- }
-#remotesubscribe {
- width: 96px;
- height: 22px;
- padding: 5px 0 0 0;
- text-align: center;
- }
-#subscribe .button:hover, #unsubscribe .button:hover {
- background-color: #904632;
- cursor: pointer;
- }
-
-a#remotesubscribe {
- display: block;
-}
-
-/* ----- Login Form -----*/
-input#license {
- width: auto;
- border: 0;
- }
-/* ----- Avatar Form -----*/
-form {
- clear: left;
-}
-
-/* ----- OpenID Form -----*/
-
-input#openid_url {
- background: url(login-bg.gif) no-repeat;
- background-color: #fff;
- background-position: 4px 50%;
- color: #000;
- padding-left: 24px;
-}
-
-/* People lists (search results, maybe subscribers) */
-
-#profiles {
- clear: both;
- margin: 0 auto;
- padding: 0;
- list-style-type: none;
- width: 540px;
- border-top: 1px solid #D8E2D7;
- /*border: 1px solid #F00;*/
- }
-#profiles a:hover {
- text-decoration: underline;
- }
-
-.profile_single {
- clear: both;
- display: block;
- margin: 0;
- padding: 5px 5px 5px 0;
- min-height: 48px;
- font-family: Georgia, "Times New Roman", Times, serif;
- font-size: 13px;
- line-height: 16px;
- border-bottom: 1px solid #D8E2D7;
- }
-.profile_single:hover {
- background-color: #F3F8EA;
- }
-
-/* ----- IM Settings Form -----*/
-
-#imsettings p {
- margin: 0;
- padding: 0;
- line-height: 15px;
-}
-
-/* ===== End Forms Styling ===== */
-
-/* ===== Tag Cloud Styling ===== */
-
-p.tagcloud {
-text-align: center;
-}
-
-p.tagcloud a {
-line-height:100%;
-vertical-align:middle;
-}
-
-p.tagcloud a.largest {
-font-size: 400%;
-}
-p.tagcloud a.verylarge {
-font-size: 300%;
-}
-
-p.tagcloud a.large {
-font-size: 200%;
-}
-
-p.tagcloud a.medium {
-font-size: 150%;
-}
-
-p.tagcloud a.small {
-font-size: 100%;
-}
-
-p.tagcloud a.verysmall {
-font-size: 80%;
-}
-
-p.tagcloud a.smallest {
-font-size: 60%;
-}
diff --git a/theme/iphone/ie6.css.dist b/theme/iphone/ie6.css.dist
deleted file mode 100644
index 97d9fee3f..000000000
--- a/theme/iphone/ie6.css.dist
+++ /dev/null
@@ -1,63 +0,0 @@
-@charset "UTF-8";
-/* CSS Document */
-body {
- text-align: center;
-}
-input {
- height: 24px;
-}
-#wrap {
- margin: 0 auto;
- padding: 0 20px;
- width: 800px;
- text-align: left;
- background: url(bg-header.gif) repeat-x #FCFFF5;
- }
-#header {
- position: relative;
- margin-left: 108px;
- }
-#nav_views {
- margin: 0;
- }
-#nav_views li {
- line-height: 19px;
- }
-.statistics dd {
- margin-top: -15px;
- clear: both;
- }
-#notices {
- margin: 0;
- }
-.notice_single {
- height: 48px;
- }
-#profile p.notice_current {
- height: 96px;
- }
-
-#subscriptions_avatars li {
- float: left;
- margin: 0;
- padding: 0;
- }
-img.avatar.original, img.avatar.profile {
- clear: none;
- float: left;
-}
-#status_textarea {
- height: 46px;
- }
-
-#nav_pagination li a {
- padding: 6px 15px;
- line-height: 27px;
- }
-#nav_sub {
- position: relative;
- margin-left: 108px;
- }
-#footer {
- margin-left: 108px;
-}
diff --git a/theme/iphone/ie7.css.dist b/theme/iphone/ie7.css.dist
deleted file mode 100644
index bbf52d5cf..000000000
--- a/theme/iphone/ie7.css.dist
+++ /dev/null
@@ -1,20 +0,0 @@
-@charset "UTF-8";
-/* CSS Document */
-
-#statistics dd {
- clear: both;
- }
-
-
-#subscriptions_avatars li {
- float: left;
- }
-img.avatar.original, img.avatar.profile {
- clear: none;
- float: left;
-}
-
-#nav_pagination li a {
- padding: 6px 15px;
- line-height: 27px;
- } \ No newline at end of file
diff --git a/theme/iphone/login-bg.gif b/theme/iphone/login-bg.gif
deleted file mode 100644
index e2d8377db..000000000
--- a/theme/iphone/login-bg.gif
+++ /dev/null
Binary files differ
diff --git a/theme/iphone/logo.png b/theme/iphone/logo.png
deleted file mode 100644
index 3b271814d..000000000
--- a/theme/iphone/logo.png
+++ /dev/null
Binary files differ