diff options
Diffstat (limited to 'lib/apiaction.php')
-rw-r--r-- | lib/apiaction.php | 318 |
1 files changed, 244 insertions, 74 deletions
diff --git a/lib/apiaction.php b/lib/apiaction.php index eef0ba637..8de13a62d 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -28,10 +28,72 @@ * @author Toby Inkster <mail@tobyinkster.co.uk> * @author Zach Copley <zach@status.net> * @copyright 2009 StatusNet, Inc. + * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ +/* External API usage documentation. Please update when you change how the API works. */ + +/*! @mainpage StatusNet REST API + + @section Introduction + + Some explanatory text about the API would be nice. + + @section API Methods + + @subsection timelinesmethods_sec Timeline Methods + + @li @ref publictimeline + @li @ref friendstimeline + + @subsection statusmethods_sec Status Methods + + @li @ref statusesupdate + + @subsection usermethods_sec User Methods + + @subsection directmessagemethods_sec Direct Message Methods + + @subsection friendshipmethods_sec Friendship Methods + + @subsection socialgraphmethods_sec Social Graph Methods + + @subsection accountmethods_sec Account Methods + + @subsection favoritesmethods_sec Favorites Methods + + @subsection blockmethods_sec Block Methods + + @subsection oauthmethods_sec OAuth Methods + + @subsection helpmethods_sec Help Methods + + @subsection groupmethods_sec Group Methods + + @page apiroot API Root + + The URLs for methods referred to in this API documentation are + relative to the StatusNet API root. The API root is determined by the + site's @b server and @b path variables, which are generally specified + in config.php. For example: + + @code + $config['site']['server'] = 'example.org'; + $config['site']['path'] = 'statusnet' + @endcode + + The pattern for a site's API root is: @c protocol://server/path/api E.g: + + @c http://example.org/statusnet/api + + The @b path can be empty. In that case the API root would simply be: + + @c http://example.org/api + +*/ + if (!defined('STATUSNET')) { exit(1); } @@ -63,9 +125,12 @@ class ApiAction extends Action var $count = null; var $max_id = null; var $since_id = null; + var $source = null; var $access = self::READ_ONLY; // read (default) or read-write + static $reserved_sources = array('web', 'omb', 'ostatus', 'mail', 'xmpp', 'api'); + /** * Initialization. * @@ -86,7 +151,13 @@ class ApiAction extends Action $this->since_id = (int)$this->arg('since_id', 0); if ($this->arg('since')) { - $this->clientError(_("since parameter is disabled for performance; use since_id"), 403); + header('X-StatusNet-Warning: since parameter is disabled; use since_id'); + } + + $this->source = $this->trimmed('source'); + + if (empty($this->source) || in_array($this->source, self::$reserved_sources)) { + $this->source = 'api'; } return true; @@ -102,6 +173,7 @@ class ApiAction extends Action function handle($args) { + header('Access-Control-Allow-Origin: *'); parent::handle($args); } @@ -199,11 +271,13 @@ class ApiAction extends Action // Is the requesting user following this user? $twitter_user['following'] = false; + $twitter_user['statusnet:blocking'] = false; $twitter_user['notifications'] = false; if (isset($this->auth_user)) { $twitter_user['following'] = $this->auth_user->isSubscribed($profile); + $twitter_user['statusnet:blocking'] = $this->auth_user->hasBlocked($profile); // Notifications on? $sub = Subscription::pkeyGet(array('subscriber' => @@ -223,6 +297,10 @@ class ApiAction extends Action } } + // StatusNet-specific + + $twitter_user['statusnet:profile_url'] = $profile->profileurl; + return $twitter_user; } @@ -251,7 +329,23 @@ class ApiAction extends Action $twitter_status['created_at'] = $this->dateTwitter($notice->created); $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? intval($notice->reply_to) : null; - $twitter_status['source'] = $this->sourceLink($notice->source); + + $source = null; + + $ns = $notice->getSource(); + if ($ns) { + if (!empty($ns->name) && !empty($ns->url)) { + $source = '<a href="' + . htmlspecialchars($ns->url) + . '" rel="nofollow">' + . htmlspecialchars($ns->name) + . '</a>'; + } else { + $source = $ns->code; + } + } + + $twitter_status['source'] = $source; $twitter_status['id'] = intval($notice->id); $replier_profile = null; @@ -308,26 +402,41 @@ class ApiAction extends Action $twitter_status['user'] = $twitter_user; } + // StatusNet-specific + + $twitter_status['statusnet:html'] = $notice->rendered; + return $twitter_status; } function twitterGroupArray($group) { - $twitter_group=array(); - $twitter_group['id']=$group->id; - $twitter_group['url']=$group->permalink(); - $twitter_group['nickname']=$group->nickname; - $twitter_group['fullname']=$group->fullname; - $twitter_group['homepage_url']=$group->homepage_url; - $twitter_group['original_logo']=$group->original_logo; - $twitter_group['homepage_logo']=$group->homepage_logo; - $twitter_group['stream_logo']=$group->stream_logo; - $twitter_group['mini_logo']=$group->mini_logo; - $twitter_group['homepage']=$group->homepage; - $twitter_group['description']=$group->description; - $twitter_group['location']=$group->location; - $twitter_group['created']=$this->dateTwitter($group->created); - $twitter_group['modified']=$this->dateTwitter($group->modified); + $twitter_group = array(); + + $twitter_group['id'] = $group->id; + $twitter_group['url'] = $group->permalink(); + $twitter_group['nickname'] = $group->nickname; + $twitter_group['fullname'] = $group->fullname; + + if (isset($this->auth_user)) { + $twitter_group['member'] = $this->auth_user->isMember($group); + $twitter_group['blocked'] = Group_block::isBlocked( + $group, + $this->auth_user->getProfile() + ); + } + + $twitter_group['member_count'] = $group->getMemberCount(); + $twitter_group['original_logo'] = $group->original_logo; + $twitter_group['homepage_logo'] = $group->homepage_logo; + $twitter_group['stream_logo'] = $group->stream_logo; + $twitter_group['mini_logo'] = $group->mini_logo; + $twitter_group['homepage'] = $group->homepage; + $twitter_group['description'] = $group->description; + $twitter_group['location'] = $group->location; + $twitter_group['created'] = $this->dateTwitter($group->created); + $twitter_group['modified'] = $this->dateTwitter($group->modified); + return $twitter_group; } @@ -476,9 +585,13 @@ class ApiAction extends Action } } - function showTwitterXmlStatus($twitter_status, $tag='status') + function showTwitterXmlStatus($twitter_status, $tag='status', $namespaces=false) { - $this->elementStart($tag); + $attrs = array(); + if ($namespaces) { + $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/'; + } + $this->elementStart($tag, $attrs); foreach($twitter_status as $element => $value) { switch ($element) { case 'user': @@ -491,7 +604,7 @@ class ApiAction extends Action $this->showXmlAttachments($twitter_status['attachments']); break; case 'geo': - $this->showGeoRSS($value); + $this->showGeoXML($value); break; case 'retweeted_status': $this->showTwitterXmlStatus($value, 'retweeted_status'); @@ -512,9 +625,13 @@ class ApiAction extends Action $this->elementEnd('group'); } - function showTwitterXmlUser($twitter_user, $role='user') + function showTwitterXmlUser($twitter_user, $role='user', $namespaces=false) { - $this->elementStart($role); + $attrs = array(); + if ($namespaces) { + $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/'; + } + $this->elementStart($role, $attrs); foreach($twitter_user as $element => $value) { if ($element == 'status') { $this->showTwitterXmlStatus($twitter_user['status']); @@ -539,7 +656,7 @@ class ApiAction extends Action } } - function showGeoRSS($geo) + function showGeoXML($geo) { if (empty($geo)) { // empty geo element @@ -551,6 +668,17 @@ class ApiAction extends Action } } + function showGeoRSS($geo) + { + if (!empty($geo)) { + $this->element( + 'georss:point', + null, + $geo['coordinates'][0] . ' ' . $geo['coordinates'][1] + ); + } + } + function showTwitterRssItem($entry) { $this->elementStart('item'); @@ -585,7 +713,7 @@ class ApiAction extends Action { $this->initDocument('xml'); $twitter_status = $this->twitterStatusArray($notice); - $this->showTwitterXmlStatus($twitter_status); + $this->showTwitterXmlStatus($twitter_status, 'status', true); $this->endDocument('xml'); } @@ -601,7 +729,8 @@ class ApiAction extends Action { $this->initDocument('xml'); - $this->elementStart('statuses', array('type' => 'array')); + $this->elementStart('statuses', array('type' => 'array', + 'xmlns:statusnet' => 'http://status.net/schema/api/1/')); if (is_array($notice)) { foreach ($notice as $n) { @@ -619,13 +748,25 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null) + function showRssTimeline($notice, $title, $link, $subtitle, $suplink = null, $logo = null, $self = null) { $this->initDocument('rss'); $this->element('title', null, $title); $this->element('link', null, $link); + + if (!is_null($self)) { + $this->element( + 'atom:link', + array( + 'type' => 'application/rss+xml', + 'href' => $self, + 'rel' => 'self' + ) + ); + } + if (!is_null($suplink)) { // For FriendFeed's SUP protocol $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', @@ -732,8 +873,12 @@ class ApiAction extends Action function showTwitterAtomEntry($entry) { $this->elementStart('entry'); - $this->element('title', null, $entry['title']); - $this->element('content', array('type' => 'html'), $entry['content']); + $this->element('title', null, common_xml_safe_str($entry['title'])); + $this->element( + 'content', + array('type' => 'html'), + common_xml_safe_str($entry['content']) + ); $this->element('id', null, $entry['id']); $this->element('published', null, $entry['published']); $this->element('updated', null, $entry['updated']); @@ -752,9 +897,13 @@ class ApiAction extends Action $this->elementEnd('entry'); } - function showXmlDirectMessage($dm) + function showXmlDirectMessage($dm, $namespaces=false) { - $this->elementStart('direct_message'); + $attrs = array(); + if ($namespaces) { + $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/'; + } + $this->elementStart('direct_message', $attrs); foreach($dm as $element => $value) { switch ($element) { case 'sender': @@ -831,7 +980,7 @@ class ApiAction extends Action { $this->initDocument('xml'); $dmsg = $this->directMessageArray($message); - $this->showXmlDirectMessage($dmsg); + $this->showXmlDirectMessage($dmsg, true); $this->endDocument('xml'); } @@ -848,7 +997,7 @@ class ApiAction extends Action $this->initDocument('atom'); - $this->element('title', null, $title); + $this->element('title', null, common_xml_safe_str($title)); $this->element('id', null, $id); $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); @@ -858,7 +1007,7 @@ class ApiAction extends Action } $this->element('updated', null, common_date_iso8601('now')); - $this->element('subtitle', null, $subtitle); + $this->element('subtitle', null, common_xml_safe_str($subtitle)); if (is_array($group)) { foreach ($group as $g) { @@ -948,7 +1097,8 @@ class ApiAction extends Action { $this->initDocument('xml'); - $this->elementStart('users', array('type' => 'array')); + $this->elementStart('users', array('type' => 'array', + 'xmlns:statusnet' => 'http://status.net/schema/api/1/')); if (is_array($user)) { foreach ($user as $u) { @@ -1039,6 +1189,7 @@ class ApiAction extends Action $this->initTwitterAtom(); break; default: + // TRANS: Client error on an API request with an unsupported data format. $this->clientError(_('Not a supported data format.')); break; } @@ -1067,6 +1218,7 @@ class ApiAction extends Action $this->endTwitterRss(); break; default: + // TRANS: Client error on an API request with an unsupported data format. $this->clientError(_('Not a supported data format.')); break; } @@ -1138,7 +1290,14 @@ class ApiAction extends Action function initTwitterRss() { $this->startXML(); - $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); + $this->elementStart( + 'rss', + array( + 'version' => '2.0', + 'xmlns:atom' => 'http://www.w3.org/2005/Atom', + 'xmlns:georss' => 'http://www.georss.org/georss' + ) + ); $this->elementStart('channel'); Event::handle('StartApiRss', array($this)); } @@ -1176,6 +1335,7 @@ class ApiAction extends Action $this->showJsonObjects($profile_array); break; default: + // TRANS: Client error on an API request with an unsupported data format. $this->clientError(_('Not a supported data format.')); return; } @@ -1214,6 +1374,34 @@ class ApiAction extends Action } } + function getTargetProfile($id) + { + if (empty($id)) { + + // Twitter supports these other ways of passing the user ID + if (is_numeric($this->arg('id'))) { + return Profile::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return Profile::staticGet('nickname', $nickname); + } else if ($this->arg('user_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('user_id'))) { + return Profile::staticGet('id', $this->arg('user_id')); + } + } else if ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return Profile::staticGet('nickname', $nickname); + } + } else if (is_numeric($id)) { + return Profile::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + return Profile::staticGet('nickname', $nickname); + } + } + function getTargetGroup($id) { if (empty($id)) { @@ -1239,7 +1427,7 @@ class ApiAction extends Action if (empty($local)) { return null; } else { - return User_group::staticGet('id', $local->id); + return User_group::staticGet('id', $local->group_id); } } @@ -1256,43 +1444,6 @@ class ApiAction extends Action } } - function sourceLink($source) - { - $source_name = _($source); - switch ($source) { - case 'web': - case 'xmpp': - case 'mail': - case 'omb': - case 'api': - break; - default: - - $name = null; - $url = null; - - $ns = Notice_source::staticGet($source); - - if ($ns) { - $name = $ns->name; - $url = $ns->url; - } else { - $app = Oauth_application::staticGet('name', $source); - if ($app) { - $name = $app->name; - $url = $app->source_url; - } - } - - if (!empty($name) && !empty($url)) { - $source_name = '<a href="' . $url . '">' . $name . '</a>'; - } - - break; - } - return $source_name; - } - /** * Returns query argument or default value if not found. Certain * parameters used throughout the API are lightly scrubbed and @@ -1336,8 +1487,27 @@ class ApiAction extends Action } } - function getSelfUri($action, $aargs) + /** + * Calculate the complete URI that called up this action. Used for + * Atom rel="self" links. Warning: this is funky. + * + * @return string URL a URL suitable for rel="self" Atom links + */ + function getSelfUri() { + $action = mb_substr(get_class($this), 0, -6); // remove 'Action' + + $id = $this->arg('id'); + $aargs = array('format' => $this->format); + if (!empty($id)) { + $aargs['id'] = $id; + } + + $tag = $this->arg('tag'); + if (!empty($tag)) { + $aargs['tag'] = $tag; + } + parse_str($_SERVER['QUERY_STRING'], $params); $pstring = ''; if (!empty($params)) { |