X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=lib%2Fapiaction.php;h=7868ecab157c178654fdd81345e2c0808daadd37;hb=0b53b6768e03932f4beec6b6655763e6ecedc36d;hp=f3efff40212abc0d03123157c806178829692fb8;hpb=0dfef88cacde19cf0afaefbd422a7f5230091064;p=quix0rs-gnu-social.git diff --git a/lib/apiaction.php b/lib/apiaction.php index f3efff4021..14e21ae4fd 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -27,15 +27,79 @@ * @author Jeffery To * @author Toby Inkster * @author Zach Copley - * @copyright 2009 StatusNet, Inc. + * @copyright 2009-2010 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); } +class ApiValidationException extends Exception { } + /** * Contains most of the Twitter-compatible API output functions. * @@ -50,7 +114,6 @@ if (!defined('STATUSNET')) { * @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 { const READ_ONLY = 1; @@ -64,6 +127,7 @@ class ApiAction extends Action var $max_id = null; var $since_id = null; var $source = null; + var $callback = null; var $access = self::READ_ONLY; // read (default) or read-write @@ -76,13 +140,13 @@ class ApiAction extends Action * * @return boolean false if user doesn't exist */ - function prepare($args) { StatusNet::setApi(true); // reduce exception reports to aid in debugging parent::prepare($args); $this->format = $this->arg('format'); + $this->callback = $this->arg('callback'); $this->page = (int)$this->arg('page', 1); $this->count = (int)$this->arg('count', 20); $this->max_id = (int)$this->arg('max_id', 0); @@ -108,9 +172,9 @@ class ApiAction extends Action * * @return void */ - function handle($args) { + header('Access-Control-Allow-Origin: *'); parent::handle($args); } @@ -138,6 +202,8 @@ class ApiAction extends Action { $twitter_user = array(); + $user = $profile->getUser(); + $twitter_user['id'] = intval($profile->id); $twitter_user['name'] = $profile->getBestName(); $twitter_user['screen_name'] = $profile->nickname; @@ -149,34 +215,11 @@ class ApiAction extends Action Avatar::defaultImage(AVATAR_STREAM_SIZE); $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; - $twitter_user['protected'] = false; # not supported by StatusNet yet + $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false; $twitter_user['followers_count'] = $profile->subscriberCount(); - $design = null; - $user = $profile->getUser(); - // Note: some profiles don't have an associated user - $defaultDesign = Design::siteDesign(); - - if (!empty($user)) { - $design = $user->getDesign(); - } - - if (empty($design)) { - $design = $defaultDesign; - } - - $color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor); - $twitter_user['profile_background_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $color = Design::toWebColor(empty($design->textcolor) ? $defaultDesign->textcolor : $design->textcolor); - $twitter_user['profile_text_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $color = Design::toWebColor(empty($design->linkcolor) ? $defaultDesign->linkcolor : $design->linkcolor); - $twitter_user['profile_link_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $color = Design::toWebColor(empty($design->sidebarcolor) ? $defaultDesign->sidebarcolor : $design->sidebarcolor); - $twitter_user['profile_sidebar_fill_color'] = ($color == null) ? '' : '#'.$color->hexValue(); - $twitter_user['profile_sidebar_border_color'] = ''; - $twitter_user['friends_count'] = $profile->subscriptionCount(); $twitter_user['created_at'] = $this->dateTwitter($profile->created); @@ -194,25 +237,17 @@ class ApiAction extends Action $twitter_user['utc_offset'] = $t->format('Z'); $twitter_user['time_zone'] = $timezone; - - $twitter_user['profile_background_image_url'] - = empty($design->backgroundimage) - ? '' : ($design->disposition & BACKGROUND_ON) - ? Design::url($design->backgroundimage) : ''; - - $twitter_user['profile_background_tile'] - = empty($design->disposition) - ? '' : ($design->disposition & BACKGROUND_TILE) ? 'true' : 'false'; - $twitter_user['statuses_count'] = $profile->noticeCount(); // 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' => @@ -227,11 +262,15 @@ class ApiAction extends Action if ($get_notice) { $notice = $profile->getCurrentNotice(); if ($notice) { - # don't get user! + // don't get user! $twitter_user['status'] = $this->twitterStatusArray($notice, false); } } + // StatusNet-specific + + $twitter_user['statusnet_profile_url'] = $profile->profileurl; + return $twitter_user; } @@ -266,13 +305,17 @@ class ApiAction extends Action $ns = $notice->getSource(); if ($ns) { if (!empty($ns->name) && !empty($ns->url)) { - $source = '' . $ns->name . ''; + $source = '' + . htmlspecialchars($ns->name) + . ''; } else { $source = $ns->code; } } - $twitter_status['source'] = htmlentities($source); + $twitter_status['source'] = $source; $twitter_status['id'] = intval($notice->id); $replier_profile = null; @@ -324,30 +367,47 @@ class ApiAction extends Action } if ($include_user && $profile) { - # Don't get notice (recursive!) + // Don't get notice (recursive!) $twitter_user = $this->twitterUserArray($profile, false); $twitter_status['user'] = $twitter_user; } + // StatusNet-specific + + $twitter_status['statusnet_html'] = $notice->rendered; + $twitter_status['statusnet_conversation_id'] = intval($notice->conversation); + 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['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'] = intval($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; } @@ -369,67 +429,98 @@ class ApiAction extends Action return $entry; } + function twitterListArray($list) + { + $profile = Profile::staticGet('id', $list->tagger); + + $twitter_list = array(); + $twitter_list['id'] = $list->id; + $twitter_list['name'] = $list->tag; + $twitter_list['full_name'] = '@'.$profile->nickname.'/'.$list->tag;; + $twitter_list['slug'] = $list->tag; + $twitter_list['description'] = $list->description; + $twitter_list['subscriber_count'] = $list->subscriberCount(); + $twitter_list['member_count'] = $list->taggedCount(); + $twitter_list['uri'] = $list->getUri(); + + if (isset($this->auth_user)) { + $twitter_list['following'] = $list->hasSubscriber($this->auth_user); + } else { + $twitter_list['following'] = false; + } + + $twitter_list['mode'] = ($list->private) ? 'private' : 'public'; + $twitter_list['user'] = $this->twitterUserArray($profile, false); + + return $twitter_list; + } + function twitterRssEntryArray($notice) { - $profile = $notice->getProfile(); $entry = array(); - // We trim() to avoid extraneous whitespace in the output + if (Event::handle('StartRssEntryArray', array($notice, &$entry))) { + $profile = $notice->getProfile(); - $entry['content'] = common_xml_safe_str(trim($notice->rendered)); - $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content)); - $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id)); - $entry['published'] = common_date_iso8601($notice->created); + // We trim() to avoid extraneous whitespace in the output - $taguribase = TagURI::base(); - $entry['id'] = "tag:$taguribase:$entry[link]"; + $entry['content'] = common_xml_safe_str(trim($notice->rendered)); + $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content)); + $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id)); + $entry['published'] = common_date_iso8601($notice->created); - $entry['updated'] = $entry['published']; - $entry['author'] = $profile->getBestName(); + $taguribase = TagURI::base(); + $entry['id'] = "tag:$taguribase:$entry[link]"; - // Enclosures - $attachments = $notice->attachments(); - $enclosures = array(); - - foreach ($attachments as $attachment) { - $enclosure_o=$attachment->getEnclosure(); - if ($enclosure_o) { - $enclosure = array(); - $enclosure['url'] = $enclosure_o->url; - $enclosure['mimetype'] = $enclosure_o->mimetype; - $enclosure['size'] = $enclosure_o->size; - $enclosures[] = $enclosure; - } - } + $entry['updated'] = $entry['published']; + $entry['author'] = $profile->getBestName(); - if (!empty($enclosures)) { - $entry['enclosures'] = $enclosures; - } + // Enclosures + $attachments = $notice->attachments(); + $enclosures = array(); - // Tags/Categories - $tag = new Notice_tag(); - $tag->notice_id = $notice->id; - if ($tag->find()) { - $entry['tags']=array(); - while ($tag->fetch()) { - $entry['tags'][]=$tag->tag; + foreach ($attachments as $attachment) { + $enclosure_o=$attachment->getEnclosure(); + if ($enclosure_o) { + $enclosure = array(); + $enclosure['url'] = $enclosure_o->url; + $enclosure['mimetype'] = $enclosure_o->mimetype; + $enclosure['size'] = $enclosure_o->size; + $enclosures[] = $enclosure; + } } - } - $tag->free(); - // RSS Item specific - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($notice->created); - $entry['guid'] = $entry['link']; + if (!empty($enclosures)) { + $entry['enclosures'] = $enclosures; + } - if (isset($notice->lat) && isset($notice->lon)) { - // This is the format that GeoJSON expects stuff to be in. - // showGeoRSS() below uses it for XML output, so we reuse it - $entry['geo'] = array('type' => 'Point', - 'coordinates' => array((float) $notice->lat, - (float) $notice->lon)); - } else { - $entry['geo'] = null; + // Tags/Categories + $tag = new Notice_tag(); + $tag->notice_id = $notice->id; + if ($tag->find()) { + $entry['tags']=array(); + while ($tag->fetch()) { + $entry['tags'][]=$tag->tag; + } + } + $tag->free(); + + // RSS Item specific + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($notice->created); + $entry['guid'] = $entry['link']; + + if (isset($notice->lat) && isset($notice->lon)) { + // This is the format that GeoJSON expects stuff to be in. + // showGeoRSS() below uses it for XML output, so we reuse it + $entry['geo'] = array('type' => 'Point', + 'coordinates' => array((float) $notice->lat, + (float) $notice->lon)); + } else { + $entry['geo'] = null; + } + + Event::handle('EndRssEntryArray', array($notice, &$entry)); } return $entry; @@ -458,7 +549,6 @@ class ApiAction extends Action $notifications = false; if ($source->isSubscribed($target)) { - $sub = Subscription::pkeyGet(array('subscriber' => $source->id, 'subscribed' => $target->id)); @@ -469,7 +559,7 @@ class ApiAction extends Action $details['notifications_enabled'] = $notifications; $details['blocking'] = $source->hasBlocked($target); - $details['id'] = $source->id; + $details['id'] = intval($source->id); return $details; } @@ -496,9 +586,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': @@ -517,7 +611,11 @@ class ApiAction extends Action $this->showTwitterXmlStatus($value, 'retweeted_status'); break; default: - $this->element($element, null, $value); + if (strncmp($element, 'statusnet_', 10) == 0) { + $this->element('statusnet:'.substr($element, 10), null, $value); + } else { + $this->element($element, null, $value); + } } } $this->elementEnd($tag); @@ -532,12 +630,32 @@ class ApiAction extends Action $this->elementEnd('group'); } - function showTwitterXmlUser($twitter_user, $role='user') + function showTwitterXmlList($twitter_list) { - $this->elementStart($role); + $this->elementStart('list'); + foreach($twitter_list as $element => $value) { + if($element == 'user') { + $this->showTwitterXmlUser($value, 'user'); + } + else { + $this->element($element, null, $value); + } + } + $this->elementEnd('list'); + } + + function showTwitterXmlUser($twitter_user, $role='user', $namespaces=false) + { + $attrs = array(); + 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']); + } else if (strncmp($element, 'statusnet_', 10) == 0) { + $this->element('statusnet:'.substr($element, 10), null, $value); } else { $this->element($element, null, $value); } @@ -591,7 +709,7 @@ class ApiAction extends Action $this->element('guid', null, $entry['guid']); $this->element('link', null, $entry['link']); - # RSS only supports 1 enclosure per item + // RSS only supports 1 enclosure per item if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ $enclosure = $entry['enclosures'][0]; $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); @@ -616,10 +734,16 @@ 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'); } + function showSingleAtomStatus($notice) + { + header('Content-Type: application/atom+xml; charset=utf-8'); + print $notice->asAtomEntry(true, true, true, $this->auth_user); + } + function show_single_json_status($notice) { $this->initDocument('json'); @@ -630,19 +754,21 @@ class ApiAction extends Action function showXmlTimeline($notice) { - $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) { - $twitter_status = $this->twitterStatusArray($n); - $this->showTwitterXmlStatus($twitter_status); - } - } else { - while ($notice->fetch()) { + $notice = new ArrayWrapper($notice); + } + + while ($notice->fetch()) { + try { $twitter_status = $this->twitterStatusArray($notice); $this->showTwitterXmlStatus($twitter_status); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + continue; } } @@ -652,7 +778,6 @@ class ApiAction extends Action function showRssTimeline($notice, $title, $link, $subtitle, $suplink = null, $logo = null, $self = null) { - $this->initDocument('rss'); $this->element('title', null, $title); @@ -690,14 +815,16 @@ class ApiAction extends Action $this->element('ttl', null, '40'); if (is_array($notice)) { - foreach ($notice as $n) { - $entry = $this->twitterRssEntryArray($n); - $this->showTwitterRssItem($entry); - } - } else { - while ($notice->fetch()) { + $notice = new ArrayWrapper($notice); + } + + while ($notice->fetch()) { + try { $entry = $this->twitterRssEntryArray($notice); $this->showTwitterRssItem($entry); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + // continue on exceptions } } @@ -706,7 +833,6 @@ class ApiAction extends Action function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null) { - $this->initDocument('atom'); $this->element('title', null, $title); @@ -718,7 +844,7 @@ class ApiAction extends Action } if (!is_null($suplink)) { - # For FriendFeed's SUP protocol + // For FriendFeed's SUP protocol $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', 'href' => $suplink, 'type' => 'application/json')); @@ -733,22 +859,23 @@ class ApiAction extends Action $this->element('subtitle', null, $subtitle); if (is_array($notice)) { - foreach ($notice as $n) { - $this->raw($n->asAtomEntry()); - } - } else { - while ($notice->fetch()) { + $notice = new ArrayWrapper($notice); + } + + while ($notice->fetch()) { + try { $this->raw($notice->asAtomEntry()); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + continue; } } $this->endDocument('atom'); - } function showRssGroups($group, $title, $link, $subtitle) { - $this->initDocument('rss'); $this->element('title', null, $title); @@ -799,9 +926,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': @@ -826,10 +957,10 @@ class ApiAction extends Action $from_profile = $message->getFrom(); $to_profile = $message->getTo(); - $dmsg['id'] = $message->id; - $dmsg['sender_id'] = $message->from_profile; + $dmsg['id'] = intval($message->id); + $dmsg['sender_id'] = intval($from_profile->id); $dmsg['text'] = trim($message->content); - $dmsg['recipient_id'] = $message->to_profile; + $dmsg['recipient_id'] = intval($to_profile->id); $dmsg['created_at'] = $this->dateTwitter($message->created); $dmsg['sender_screen_name'] = $from_profile->nickname; $dmsg['recipient_screen_name'] = $to_profile->nickname; @@ -878,7 +1009,7 @@ class ApiAction extends Action { $this->initDocument('xml'); $dmsg = $this->directMessageArray($message); - $this->showXmlDirectMessage($dmsg); + $this->showXmlDirectMessage($dmsg, true); $this->endDocument('xml'); } @@ -892,7 +1023,6 @@ class ApiAction extends Action function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null) { - $this->initDocument('atom'); $this->element('title', null, common_xml_safe_str($title)); @@ -923,20 +1053,21 @@ class ApiAction extends Action function showJsonTimeline($notice) { - $this->initDocument('json'); $statuses = array(); if (is_array($notice)) { - foreach ($notice as $n) { - $twitter_status = $this->twitterStatusArray($n); - array_push($statuses, $twitter_status); - } - } else { - while ($notice->fetch()) { + $notice = new ArrayWrapper($notice); + } + + while ($notice->fetch()) { + try { $twitter_status = $this->twitterStatusArray($notice); array_push($statuses, $twitter_status); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + continue; } } @@ -947,7 +1078,6 @@ class ApiAction extends Action function showJsonGroups($group) { - $this->initDocument('json'); $groups = array(); @@ -991,11 +1121,70 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showTwitterXmlUsers($user) + function showXmlLists($list, $next_cursor=0, $prev_cursor=0) + { + + $this->initDocument('xml'); + $this->elementStart('lists_list'); + $this->elementStart('lists', array('type' => 'array')); + + if (is_array($list)) { + foreach ($list as $l) { + $twitter_list = $this->twitterListArray($l); + $this->showTwitterXmlList($twitter_list); + } + } else { + while ($list->fetch()) { + $twitter_list = $this->twitterListArray($list); + $this->showTwitterXmlList($twitter_list); + } + } + + $this->elementEnd('lists'); + + $this->element('next_cursor', null, $next_cursor); + $this->element('previous_cursor', null, $prev_cursor); + + $this->elementEnd('lists_list'); + $this->endDocument('xml'); + } + + function showJsonLists($list, $next_cursor=0, $prev_cursor=0) { + $this->initDocument('json'); + + $lists = array(); + if (is_array($list)) { + foreach ($list as $l) { + $twitter_list = $this->twitterListArray($l); + array_push($lists, $twitter_list); + } + } else { + while ($list->fetch()) { + $twitter_list = $this->twitterListArray($list); + array_push($lists, $twitter_list); + } + } + + $lists_list = array( + 'lists' => $lists, + 'next_cursor' => $next_cursor, + 'next_cursor_str' => strval($next_cursor), + 'previous_cursor' => $prev_cursor, + 'previous_cursor_str' => strval($prev_cursor) + ); + + $this->showJsonObjects($lists_list); + + $this->endDocument('json'); + } + + function showTwitterXmlUsers($user) + { $this->initDocument('xml'); - $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) { @@ -1015,7 +1204,6 @@ class ApiAction extends Action function showJsonUsers($user) { - $this->initDocument('json'); $users = array(); @@ -1053,6 +1241,22 @@ class ApiAction extends Action $this->endDocument('xml'); } + function showSingleJsonList($list) + { + $this->initDocument('json'); + $twitter_list = $this->twitterListArray($list); + $this->showJsonObjects($twitter_list); + $this->endDocument('json'); + } + + function showSingleXmlList($list) + { + $this->initDocument('xml'); + $twitter_list = $this->twitterListArray($list); + $this->showTwitterXmlList($twitter_list); + $this->endDocument('xml'); + } + function dateTwitter($dt) { $dateStr = date('d F Y H:i:s', strtotime($dt)); @@ -1072,9 +1276,8 @@ class ApiAction extends Action header('Content-Type: application/json; charset=utf-8'); // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { - print $callback . '('; + if (isset($this->callback)) { + print $this->callback . '('; } break; case 'rss': @@ -1086,6 +1289,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; } @@ -1100,10 +1304,8 @@ class ApiAction extends Action $this->endXML(); break; case 'json': - // Check for JSONP callback - $callback = $this->arg('callback'); - if ($callback) { + if (isset($this->callback)) { print ')'; } break; @@ -1114,15 +1316,19 @@ 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; } return; } - function clientError($msg, $code = 400, $format = 'xml') + function clientError($msg, $code = 400, $format = null) { $action = $this->trimmed('action'); + if ($format === null) { + $format = $this->format; + } common_debug("User error '$code' on '$action': $msg", __FILE__); @@ -1132,30 +1338,42 @@ class ApiAction extends Action $status_string = ClientErrorAction::$status[$code]; - header('HTTP/1.1 '.$code.' '.$status_string); + // Do not emit error header for JSONP + if (!isset($this->callback)) { + header('HTTP/1.1 ' . $code . ' ' . $status_string); + } - if ($format == 'xml') { + switch($format) { + case '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'){ + break; + case 'json': $this->initDocument('json'); $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); print(json_encode($error_array)); $this->endDocument('json'); - } else { - + break; + case 'text': + header('Content-Type: text/plain; charset=utf-8'); + print $msg; + break; + default: // 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 = 'xml') + function serverError($msg, $code = 500, $content_type = null) { $action = $this->trimmed('action'); + if ($content_type === null) { + $content_type = $this->format; + } common_debug("Server error '$code' on '$action': $msg", __FILE__); @@ -1165,7 +1383,10 @@ class ApiAction extends Action $status_string = ServerErrorAction::$status[$code]; - header('HTTP/1.1 '.$code.' '.$status_string); + // Do not emit error header for JSONP + if (!isset($this->callback)) { + header('HTTP/1.1 '.$code.' '.$status_string); + } if ($content_type == 'xml') { $this->initDocument('xml'); @@ -1230,18 +1451,23 @@ 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; } return; } + private static function is_decimal($str) + { + return preg_match('/^[0-9]+$/', $str); + } + function getTargetUser($id) { if (empty($id)) { - // Twitter supports these other ways of passing the user ID - if (is_numeric($this->arg('id'))) { + if (self::is_decimal($this->arg('id'))) { return User::staticGet($this->arg('id')); } else if ($this->arg('id')) { $nickname = common_canonical_nickname($this->arg('id')); @@ -1249,7 +1475,7 @@ class ApiAction extends Action } 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'))) { + if (self::is_decimal($this->arg('user_id'))) { return User::staticGet('id', $this->arg('user_id')); } } else if ($this->arg('screen_name')) { @@ -1260,7 +1486,7 @@ class ApiAction extends Action return $this->auth_user; } - } else if (is_numeric($id)) { + } else if (self::is_decimal($id)) { return User::staticGet($id); } else { $nickname = common_canonical_nickname($id); @@ -1268,46 +1494,94 @@ class ApiAction extends Action } } - function getTargetGroup($id) + function getTargetProfile($id) { if (empty($id)) { - if (is_numeric($this->arg('id'))) { - return User_group::staticGet($this->arg('id')); + + // Twitter supports these other ways of passing the user ID + if (self::is_decimal($this->arg('id'))) { + return Profile::staticGet($this->arg('id')); } else if ($this->arg('id')) { + // Screen names currently can only uniquely identify a local user. $nickname = common_canonical_nickname($this->arg('id')); - $local = Local_group::staticGet('nickname', $nickname); - if (empty($local)) { - return null; - } else { - return User_group::staticGet('id', $local->id); - } - } else if ($this->arg('group_id')) { + $user = User::staticGet('nickname', $nickname); + return $user ? $user->getProfile() : null; + } 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('group_id'))) { + if (self::is_decimal($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')); + $user = User::staticGet('nickname', $nickname); + return $user ? $user->getProfile() : null; + } + } else if (self::is_decimal($id)) { + return Profile::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + $user = User::staticGet('nickname', $nickname); + return $user ? $user->getProfile() : null; + } + } + + function getTargetGroup($id) + { + if (empty($id)) { + if (self::is_decimal($this->arg('id'))) { + return User_group::staticGet('id', $this->arg('id')); + } else if ($this->arg('id')) { + return User_group::getForNickname($this->arg('id')); + } else if ($this->arg('group_id')) { + // This is to ensure that a non-numeric group_id still + // overrides group_name even if it doesn't get used + if (self::is_decimal($this->arg('group_id'))) { return User_group::staticGet('id', $this->arg('group_id')); } } else if ($this->arg('group_name')) { - $nickname = common_canonical_nickname($this->arg('group_name')); - $local = Local_group::staticGet('nickname', $nickname); - if (empty($local)) { - return null; - } else { - return User_group::staticGet('id', $local->group_id); - } + return User_group::getForNickname($this->arg('group_name')); } - } else if (is_numeric($id)) { - return User_group::staticGet($id); + } else if (self::is_decimal($id)) { + return User_group::staticGet('id', $id); } else { - $nickname = common_canonical_nickname($id); - $local = Local_group::staticGet('nickname', $nickname); - if (empty($local)) { - return null; + return User_group::getForNickname($id); + } + } + + function getTargetList($user=null, $id=null) + { + $tagger = $this->getTargetUser($user); + $list = null; + + if (empty($id)) { + $id = $this->arg('id'); + } + + if($id) { + if (is_numeric($id)) { + $list = Profile_list::staticGet('id', $id); + + // only if the list with the id belongs to the tagger + if(empty($list) || $list->tagger != $tagger->id) { + $list = null; + } + } + if (empty($list)) { + $tag = common_canonical_tag($id); + $list = Profile_list::getByTaggerAndTag($tagger->id, $tag); + } + + if (!empty($list) && $list->private) { + if ($this->auth_user->id == $list->tagger) { + return $list; + } } else { - return User_group::staticGet('id', $local->group_id); + return $list; } } + return null; } /** @@ -1322,7 +1596,6 @@ class ApiAction extends Action */ function arg($key, $def=null) { - // XXX: Do even more input validation/scrubbing? if (array_key_exists($key, $this->args)) { @@ -1389,5 +1662,4 @@ class ApiAction extends Action return $uri; } - }