From fe411e81387e082a627324345552af0badd716dd Mon Sep 17 00:00:00 2001 From: Diogo Cordeiro Date: Fri, 3 May 2019 17:09:23 +0100 Subject: [PATCH] Fix some typos/small obvious bugs on twitterRssGroupArray --- lib/apiaction.php | 1493 ++++++++++++++++++++++----------------------- 1 file changed, 743 insertions(+), 750 deletions(-) diff --git a/lib/apiaction.php b/lib/apiaction.php index 18ec44bb2b..e285489007 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -98,7 +98,9 @@ if (!defined('STATUSNET')) { exit(1); } -class ApiValidationException extends Exception { } +class ApiValidationException extends Exception +{ +} /** * Contains most of the Twitter-compatible API output functions. @@ -116,86 +118,90 @@ class ApiValidationException extends Exception { } */ class ApiAction extends Action { - const READ_ONLY = 1; + const READ_ONLY = 1; const READ_WRITE = 2; + public static $reserved_sources = array('web', 'omb', 'ostatus', 'mail', 'xmpp', 'api'); + public $user = null; + public $auth_user = null; + public $page = null; + public $count = null; + public $offset = null; + public $limit = null; + public $max_id = null; + public $since_id = null; + public $source = null; + public $callback = null; + public $format = null; // read (default) or read-write + public $access = self::READ_ONLY; - var $user = null; - var $auth_user = null; - var $page = null; - var $count = null; - var $offset = null; - var $limit = null; - var $max_id = null; - var $since_id = null; - var $source = null; - var $callback = null; - var $format = null; - - var $access = self::READ_ONLY; // read (default) or read-write - - static $reserved_sources = array('web', 'omb', 'ostatus', 'mail', 'xmpp', 'api'); - - /** - * Initialization. - * - * @param array $args Web and URL arguments - * - * @return boolean false if user doesn't exist - */ - protected function prepare(array $args=array()) + public function twitterRelationshipArray($source, $target) { - GNUsocial::setApi(true); // reduce exception reports to aid in debugging - parent::prepare($args); + $relationship = array(); - $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); - $this->since_id = (int)$this->arg('since_id', 0); + $relationship['source'] = + $this->relationshipDetailsArray($source->getProfile(), $target->getProfile()); + $relationship['target'] = + $this->relationshipDetailsArray($target->getProfile(), $source->getProfile()); - // These two are not used everywhere, mainly just AtompubAction extensions - $this->offset = ($this->page-1) * $this->count; - $this->limit = $this->count + 1; - - if ($this->arg('since')) { - header('X-GNUsocial-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; + return array('relationship' => $relationship); } - /** - * Handle a request - * - * @param array $args Arguments from $_REQUEST - * - * @return void - */ - protected function handle() + public function relationshipDetailsArray(Profile $source, Profile $target) { - header('Access-Control-Allow-Origin: *'); - parent::handle(); + $details = array(); + + $details['screen_name'] = $source->getNickname(); + $details['followed_by'] = $target->isSubscribed($source); + + try { + $sub = Subscription::getSubscription($source, $target); + $details['following'] = true; + $details['notifications_enabled'] = ($sub->jabber || $sub->sms); + } catch (NoResultException $e) { + $details['following'] = false; + $details['notifications_enabled'] = false; + } + + $details['blocking'] = $source->hasBlocked($target); + $details['id'] = intval($source->id); + + return $details; + } + + public function showTwitterXmlRelationship($relationship) + { + $this->elementStart('relationship'); + + foreach ($relationship as $element => $value) { + if ($element == 'source' || $element == 'target') { + $this->elementStart($element); + $this->showXmlRelationshipDetails($value); + $this->elementEnd($element); + } + } + + $this->elementEnd('relationship'); + } + + public function showXmlRelationshipDetails($details) + { + foreach ($details as $element => $value) { + $this->element($element, null, $value); + } } /** * Overrides XMLOutputter::element to write booleans as strings (true|false). * See that method's documentation for more info. * - * @param string $tag Element type or tagname - * @param array $attrs Array of element attributes, as + * @param string $tag Element type or tagname + * @param array $attrs Array of element attributes, as * key-value pairs * @param string $content string content of the element * * @return void */ - function element($tag, $attrs=null, $content=null) + public function element($tag, $attrs = [], $content = "") { if (is_bool($content)) { $content = ($content ? 'true' : 'false'); @@ -204,102 +210,70 @@ class ApiAction extends Action return parent::element($tag, $attrs, $content); } - function twitterUserArray($profile, $get_notice=false) + public function showSingleXmlStatus($notice) { - $twitter_user = array(); - - try { - $user = $profile->getUser(); - } catch (NoSuchUserException $e) { - $user = null; - } - - $twitter_user['id'] = $profile->getID(); - $twitter_user['name'] = $profile->getBestName(); - $twitter_user['screen_name'] = $profile->getNickname(); - $twitter_user['location'] = $profile->location; - $twitter_user['description'] = $profile->getDescription(); - - // TODO: avatar url template (example.com/user/avatar?size={x}x{y}) - $twitter_user['profile_image_url'] = Avatar::urlByProfile($profile, AVATAR_STREAM_SIZE); - $twitter_user['profile_image_url_https'] = $twitter_user['profile_image_url']; - - // START introduced by qvitter API, not necessary for StatusNet API - $twitter_user['profile_image_url_profile_size'] = Avatar::urlByProfile($profile, AVATAR_PROFILE_SIZE); - try { - $avatar = Avatar::getUploaded($profile); - $origurl = $avatar->displayUrl(); - } catch (Exception $e) { - $origurl = $twitter_user['profile_image_url_profile_size']; - } - $twitter_user['profile_image_url_original'] = $origurl; - - $twitter_user['groups_count'] = $profile->getGroupCount(); - foreach (array('linkcolor', 'backgroundcolor') as $key) { - $twitter_user[$key] = Profile_prefs::getConfigData($profile, 'theme', $key); - } - // END introduced by qvitter API, not necessary for StatusNet API - - $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; - $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false; - $twitter_user['followers_count'] = $profile->subscriberCount(); - - // Note: some profiles don't have an associated user - - $twitter_user['friends_count'] = $profile->subscriptionCount(); - - $twitter_user['created_at'] = self::dateTwitter($profile->created); - - $timezone = 'UTC'; - - if (!empty($user) && $user->timezone) { - $timezone = $user->timezone; - } - - $t = new DateTime; - $t->setTimezone(new DateTimeZone($timezone)); - - $twitter_user['utc_offset'] = $t->format('Z'); - $twitter_user['time_zone'] = $timezone; - $twitter_user['statuses_count'] = $profile->noticeCount(); - - // Is the requesting user following this user? - // These values might actually also mean "unknown". Ambiguity issues? - $twitter_user['following'] = false; - $twitter_user['statusnet_blocking'] = false; - $twitter_user['notifications'] = false; - - if ($this->scoped instanceof Profile) { - try { - $sub = Subscription::getSubscription($this->scoped, $profile); - // Notifications on? - $twitter_user['following'] = true; - $twitter_user['notifications'] = ($sub->jabber || $sub->sms); - } catch (NoResultException $e) { - // well, the values are already false... - } - $twitter_user['statusnet_blocking'] = $this->scoped->hasBlocked($profile); - } - - if ($get_notice) { - $notice = $profile->getCurrentNotice(); - if ($notice instanceof Notice) { - // don't get user! - $twitter_user['status'] = $this->twitterStatusArray($notice, false); - } - } - - // StatusNet-specific - - $twitter_user['statusnet_profile_url'] = $profile->profileurl; - - // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array - Event::handle('TwitterUserArray', array($profile, &$twitter_user, $this->scoped, array())); - - return $twitter_user; + $this->initDocument('xml'); + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status, 'status', true); + $this->endDocument('xml'); } - function twitterStatusArray($notice, $include_user=true) + public function initDocument($type = 'xml') + { + switch ($type) { + case 'xml': + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + break; + case 'json': + header('Content-Type: application/json; charset=utf-8'); + + // Check for JSONP callback + if (isset($this->callback)) { + print $this->callback . '('; + } + break; + case 'rss': + header("Content-Type: application/rss+xml; charset=utf-8"); + $this->initTwitterRss(); + break; + case 'atom': + header('Content-Type: application/atom+xml; charset=utf-8'); + $this->initTwitterAtom(); + break; + default: + // TRANS: Client error on an API request with an unsupported data format. + $this->clientError(_('Not a supported data format.')); + } + + return; + } + + public function initTwitterRss() + { + $this->startXML(); + $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)); + } + + public function initTwitterAtom() + { + $this->startXML(); + // FIXME: don't hardcode the language here! + $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', + 'xml:lang' => 'en-US', + 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); + } + + public function twitterStatusArray($notice, $include_user = true) { $base = $this->twitterSimpleStatusArray($notice, $include_user); @@ -315,7 +289,7 @@ class ApiAction extends Action return $base; } - function twitterSimpleStatusArray($notice, $include_user=true) + public function twitterSimpleStatusArray($notice, $include_user = true) { $profile = $notice->getProfile(); @@ -373,8 +347,8 @@ class ApiAction extends Action $notloc = Notice_location::locFromStored($notice); // This is the format that GeoJSON expects stuff to be in $twitter_status['geo'] = array('type' => 'Point', - 'coordinates' => array((float) $notloc->lat, - (float) $notloc->lon)); + 'coordinates' => array((float)$notloc->lat, + (float)$notloc->lon)); } catch (ServerException $e) { $twitter_status['geo'] = null; } @@ -383,7 +357,6 @@ class ApiAction extends Action $attachments = $notice->attachments(); if (!empty($attachments)) { - $twitter_status['attachments'] = array(); foreach ($attachments as $attachment) { @@ -413,88 +386,348 @@ class ApiAction extends Action // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array Event::handle('NoticeSimpleStatusArray', array($notice, &$twitter_status, $this->scoped, - array('include_user'=>$include_user))); + array('include_user' => $include_user))); return $twitter_status; } - function twitterGroupArray($group) + public static function dateTwitter($dt) { - $twitter_group = array(); + $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'); + } - $twitter_group['id'] = intval($group->id); - $twitter_group['url'] = $group->permalink(); - $twitter_group['nickname'] = $group->nickname; - $twitter_group['fullname'] = $group->fullname; + public function twitterUserArray($profile, $get_notice = false) + { + $twitter_user = array(); + + try { + $user = $profile->getUser(); + } catch (NoSuchUserException $e) { + $user = null; + } + + $twitter_user['id'] = $profile->getID(); + $twitter_user['name'] = $profile->getBestName(); + $twitter_user['screen_name'] = $profile->getNickname(); + $twitter_user['location'] = $profile->location; + $twitter_user['description'] = $profile->getDescription(); + + // TODO: avatar url template (example.com/user/avatar?size={x}x{y}) + $twitter_user['profile_image_url'] = Avatar::urlByProfile($profile, AVATAR_STREAM_SIZE); + $twitter_user['profile_image_url_https'] = $twitter_user['profile_image_url']; + + // START introduced by qvitter API, not necessary for StatusNet API + $twitter_user['profile_image_url_profile_size'] = Avatar::urlByProfile($profile, AVATAR_PROFILE_SIZE); + try { + $avatar = Avatar::getUploaded($profile); + $origurl = $avatar->displayUrl(); + } catch (Exception $e) { + $origurl = $twitter_user['profile_image_url_profile_size']; + } + $twitter_user['profile_image_url_original'] = $origurl; + + $twitter_user['groups_count'] = $profile->getGroupCount(); + foreach (array('linkcolor', 'backgroundcolor') as $key) { + $twitter_user[$key] = Profile_prefs::getConfigData($profile, 'theme', $key); + } + // END introduced by qvitter API, not necessary for StatusNet API + + $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; + $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false; + $twitter_user['followers_count'] = $profile->subscriberCount(); + + // Note: some profiles don't have an associated user + + $twitter_user['friends_count'] = $profile->subscriptionCount(); + + $twitter_user['created_at'] = self::dateTwitter($profile->created); + + $timezone = 'UTC'; + + if (!empty($user) && $user->timezone) { + $timezone = $user->timezone; + } + + $t = new DateTime; + $t->setTimezone(new DateTimeZone($timezone)); + + $twitter_user['utc_offset'] = $t->format('Z'); + $twitter_user['time_zone'] = $timezone; + $twitter_user['statuses_count'] = $profile->noticeCount(); + + // Is the requesting user following this user? + // These values might actually also mean "unknown". Ambiguity issues? + $twitter_user['following'] = false; + $twitter_user['statusnet_blocking'] = false; + $twitter_user['notifications'] = false; if ($this->scoped instanceof Profile) { - $twitter_group['member'] = $this->scoped->isMember($group); - $twitter_group['blocked'] = Group_block::isBlocked( - $group, - $this->scoped + try { + $sub = Subscription::getSubscription($this->scoped, $profile); + // Notifications on? + $twitter_user['following'] = true; + $twitter_user['notifications'] = ($sub->jabber || $sub->sms); + } catch (NoResultException $e) { + // well, the values are already false... + } + $twitter_user['statusnet_blocking'] = $this->scoped->hasBlocked($profile); + } + + if ($get_notice) { + $notice = $profile->getCurrentNotice(); + if ($notice instanceof Notice) { + // don't get user! + $twitter_user['status'] = $this->twitterStatusArray($notice, false); + } + } + + // StatusNet-specific + + $twitter_user['statusnet_profile_url'] = $profile->profileurl; + + // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array + Event::handle('TwitterUserArray', array($profile, &$twitter_user, $this->scoped, array())); + + return $twitter_user; + } + + public function showTwitterXmlStatus($twitter_status, $tag = 'status', $namespaces = false) + { + $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': + $this->showTwitterXmlUser($twitter_status['user']); + break; + case 'text': + $this->element($element, null, common_xml_safe_str($value)); + break; + case 'attachments': + $this->showXmlAttachments($twitter_status['attachments']); + break; + case 'geo': + $this->showGeoXML($value); + break; + case 'retweeted_status': + // FIXME: MOVE TO SHARE PLUGIN + $this->showTwitterXmlStatus($value, 'retweeted_status'); + break; + default: + if (strncmp($element, 'statusnet_', 10) == 0) { + if ($element === 'statusnet_in_groups' && is_array($value)) { + // QVITTERFIX because it would cause an array to be sent as $value + // THIS IS UNDOCUMENTED AND SHOULD NEVER BE RELIED UPON (qvitter uses json output) + $value = json_encode($value); + } + $this->element('statusnet:' . substr($element, 10), null, $value); + } else { + $this->element($element, null, $value); + } + } + } + $this->elementEnd($tag); + } + + public 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']); + } elseif (strncmp($element, 'statusnet_', 10) == 0) { + $this->element('statusnet:' . substr($element, 10), null, $value); + } else { + $this->element($element, null, $value); + } + } + $this->elementEnd($role); + } + + public function showXmlAttachments($attachments) + { + if (!empty($attachments)) { + $this->elementStart('attachments', array('type' => 'array')); + foreach ($attachments as $attachment) { + $attrs = array(); + $attrs['url'] = $attachment['url']; + $attrs['mimetype'] = $attachment['mimetype']; + $attrs['size'] = $attachment['size']; + $this->element('enclosure', $attrs, ''); + } + $this->elementEnd('attachments'); + } + } + + public function showGeoXML($geo) + { + if (empty($geo)) { + // empty geo element + $this->element('geo'); + } else { + $this->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss')); + $this->element('georss:point', null, $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]); + $this->elementEnd('geo'); + } + } + + public function endDocument($type = 'xml') + { + switch ($type) { + case 'xml': + $this->endXML(); + break; + case 'json': + // Check for JSONP callback + if (isset($this->callback)) { + print ')'; + } + break; + case 'rss': + $this->endTwitterRss(); + break; + case 'atom': + $this->endTwitterRss(); + break; + default: + // TRANS: Client error on an API request with an unsupported data format. + $this->clientError(_('Not a supported data format.')); + } + return; + } + + public function endTwitterRss() + { + $this->elementEnd('channel'); + $this->elementEnd('rss'); + $this->endXML(); + } + + public function showSingleAtomStatus($notice) + { + header('Content-Type: application/atom+xml;type=entry;charset="utf-8"'); + print '' . "\n"; + print $notice->asAtomEntry(true, true, true, $this->scoped); + } + + public function show_single_json_status($notice) + { + $this->initDocument('json'); + $status = $this->twitterStatusArray($notice); + $this->showJsonObjects($status); + $this->endDocument('json'); + } + + public function showJsonObjects($objects) + { + $json_objects = json_encode($objects); + if ($json_objects === false) { + $this->clientError(_('JSON encoding failed. Error: ') . json_last_error_msg()); + } else { + print $json_objects; + } + } + + public function showXmlTimeline($notice) + { + $this->initDocument('xml'); + $this->elementStart('statuses', array('type' => 'array', + 'xmlns:statusnet' => 'http://status.net/schema/api/1/')); + + if (is_array($notice)) { + //FIXME: make everything calling showJsonTimeline use only Notice objects + $ids = array(); + foreach ($notice as $n) { + $ids[] = $n->getID(); + } + $notice = Notice::multiGet('id', $ids); + } + + while ($notice->fetch()) { + try { + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + continue; + } + } + + $this->elementEnd('statuses'); + $this->endDocument('xml'); + } + + public 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' + ) ); } - $twitter_group['admin_count'] = $group->getAdminCount(); - $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'] = self::dateTwitter($group->created); - $twitter_group['modified'] = self::dateTwitter($group->modified); - - return $twitter_group; - } - - function twitterRssGroupArray($group) - { - $entry = array(); - $entry['content']=$group->description; - $entry['title']=$group->nickname; - $entry['link']=$group->permalink(); - $entry['published']=common_date_iso8601($group->created); - $entry['updated']==common_date_iso8601($group->modified); - $taguribase = common_config('integration', 'groupuri'); - $entry['id'] = "group:$groupuribase:$entry[link]"; - - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($group->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - function twitterListArray($list) - { - $profile = Profile::getKV('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 ($this->scoped instanceof Profile) { - $twitter_list['following'] = $list->hasSubscriber($this->scoped); - } else { - $twitter_list['following'] = false; + if (!is_null($suplink)) { + // For FriendFeed's SUP protocol + $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', + 'rel' => 'http://api.friendfeed.com/2008/03#sup', + 'href' => $suplink, + 'type' => 'application/json')); } - $twitter_list['mode'] = ($list->private) ? 'private' : 'public'; - $twitter_list['user'] = $this->twitterUserArray($profile, false); + if (!is_null($logo)) { + $this->elementStart('image'); + $this->element('link', null, $link); + $this->element('title', null, $title); + $this->element('url', null, $logo); + $this->elementEnd('image'); + } - return $twitter_list; + $this->element('description', null, $subtitle); + $this->element('language', null, 'en-us'); + $this->element('ttl', null, '40'); + + if (is_array($notice)) { + //FIXME: make everything calling showJsonTimeline use only Notice objects + $ids = array(); + foreach ($notice as $n) { + $ids[] = $n->getID(); + } + $notice = Notice::multiGet('id', $ids); + } + + while ($notice->fetch()) { + try { + $entry = $this->twitterRssEntryArray($notice); + $this->showTwitterRssItem($entry); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + // continue on exceptions + } + } + + $this->endTwitterRss(); } - function twitterRssEntryArray($notice) + public function twitterRssEntryArray($notice) { $entry = array(); @@ -539,9 +772,9 @@ class ApiAction extends Action $tag = new Notice_tag(); $tag->notice_id = $notice->id; if ($tag->find()) { - $entry['tags']=array(); + $entry['tags'] = array(); while ($tag->fetch()) { - $entry['tags'][]=$tag->tag; + $entry['tags'][] = $tag->tag; } } $tag->free(); @@ -556,8 +789,8 @@ class ApiAction extends Action // 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) $notloc->lat, - (float) $notloc->lon)); + 'coordinates' => array((float)$notloc->lat, + (float)$notloc->lon)); } catch (ServerException $e) { $entry['geo'] = null; } @@ -568,172 +801,32 @@ class ApiAction extends Action return $entry; } - function twitterRelationshipArray($source, $target) + public function showTwitterRssItem($entry) { - $relationship = array(); + $this->elementStart('item'); + $this->element('title', null, $entry['title']); + $this->element('description', null, $entry['description']); + $this->element('pubDate', null, $entry['pubDate']); + $this->element('guid', null, $entry['guid']); + $this->element('link', null, $entry['link']); - $relationship['source'] = - $this->relationshipDetailsArray($source->getProfile(), $target->getProfile()); - $relationship['target'] = - $this->relationshipDetailsArray($target->getProfile(), $source->getProfile()); - - return array('relationship' => $relationship); - } - - function relationshipDetailsArray(Profile $source, Profile $target) - { - $details = array(); - - $details['screen_name'] = $source->getNickname(); - $details['followed_by'] = $target->isSubscribed($source); - - try { - $sub = Subscription::getSubscription($source, $target); - $details['following'] = true; - $details['notifications_enabled'] = ($sub->jabber || $sub->sms); - } catch (NoResultException $e) { - $details['following'] = false; - $details['notifications_enabled'] = false; + // 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); } - $details['blocking'] = $source->hasBlocked($target); - $details['id'] = intval($source->id); - - return $details; - } - - function showTwitterXmlRelationship($relationship) - { - $this->elementStart('relationship'); - - foreach($relationship as $element => $value) { - if ($element == 'source' || $element == 'target') { - $this->elementStart($element); - $this->showXmlRelationshipDetails($value); - $this->elementEnd($element); + if (array_key_exists('tags', $entry)) { + foreach ($entry['tags'] as $tag) { + $this->element('category', null, $tag); } } - $this->elementEnd('relationship'); + $this->showGeoRSS($entry['geo']); + $this->elementEnd('item'); } - function showXmlRelationshipDetails($details) - { - foreach($details as $element => $value) { - $this->element($element, null, $value); - } - } - - function showTwitterXmlStatus($twitter_status, $tag='status', $namespaces=false) - { - $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': - $this->showTwitterXmlUser($twitter_status['user']); - break; - case 'text': - $this->element($element, null, common_xml_safe_str($value)); - break; - case 'attachments': - $this->showXmlAttachments($twitter_status['attachments']); - break; - case 'geo': - $this->showGeoXML($value); - break; - case 'retweeted_status': - // FIXME: MOVE TO SHARE PLUGIN - $this->showTwitterXmlStatus($value, 'retweeted_status'); - break; - default: - if (strncmp($element, 'statusnet_', 10) == 0) { - if ($element === 'statusnet_in_groups' && is_array($value)) { - // QVITTERFIX because it would cause an array to be sent as $value - // THIS IS UNDOCUMENTED AND SHOULD NEVER BE RELIED UPON (qvitter uses json output) - $value = json_encode($value); - } - $this->element('statusnet:'.substr($element, 10), null, $value); - } else { - $this->element($element, null, $value); - } - } - } - $this->elementEnd($tag); - } - - function showTwitterXmlGroup($twitter_group) - { - $this->elementStart('group'); - foreach($twitter_group as $element => $value) { - $this->element($element, null, $value); - } - $this->elementEnd('group'); - } - - function showTwitterXmlList($twitter_list) - { - $this->elementStart('list'); - foreach($twitter_list as $element => $value) { - if($element == 'user') { - $this->showTwitterXmlUser($value, 'user'); - } - else { - $this->element($element, null, $value); - } - } - $this->elementEnd('list'); - } - - function showTwitterXmlUser($twitter_user, $role='user', $namespaces=false) - { - $attrs = array(); - 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); - } - } - $this->elementEnd($role); - } - - function showXmlAttachments($attachments) { - if (!empty($attachments)) { - $this->elementStart('attachments', array('type' => 'array')); - foreach ($attachments as $attachment) { - $attrs = array(); - $attrs['url'] = $attachment['url']; - $attrs['mimetype'] = $attachment['mimetype']; - $attrs['size'] = $attachment['size']; - $this->element('enclosure', $attrs, ''); - } - $this->elementEnd('attachments'); - } - } - - function showGeoXML($geo) - { - if (empty($geo)) { - // empty geo element - $this->element('geo'); - } else { - $this->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss')); - $this->element('georss:point', null, $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]); - $this->elementEnd('geo'); - } - } - - function showGeoRSS($geo) + public function showGeoRSS($geo) { if (!empty($geo)) { $this->element( @@ -744,155 +837,7 @@ class ApiAction extends Action } } - function showTwitterRssItem($entry) - { - $this->elementStart('item'); - $this->element('title', null, $entry['title']); - $this->element('description', null, $entry['description']); - $this->element('pubDate', null, $entry['pubDate']); - $this->element('guid', null, $entry['guid']); - $this->element('link', null, $entry['link']); - - // RSS only supports 1 enclosure per item - if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ - $enclosure = $entry['enclosures'][0]; - $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); - } - - if(array_key_exists('tags', $entry)){ - foreach($entry['tags'] as $tag){ - $this->element('category', null,$tag); - } - } - - $this->showGeoRSS($entry['geo']); - $this->elementEnd('item'); - } - - function showJsonObjects($objects) - { - $json_objects = json_encode($objects); - if($json_objects === false) { - $this->clientError(_('JSON encoding failed. Error: ').json_last_error_msg()); - } else { - print $json_objects; - } - } - - - function showSingleXmlStatus($notice) - { - $this->initDocument('xml'); - $twitter_status = $this->twitterStatusArray($notice); - $this->showTwitterXmlStatus($twitter_status, 'status', true); - $this->endDocument('xml'); - } - - function showSingleAtomStatus($notice) - { - header('Content-Type: application/atom+xml;type=entry;charset="utf-8"'); - print '' . "\n"; - print $notice->asAtomEntry(true, true, true, $this->scoped); - } - - function show_single_json_status($notice) - { - $this->initDocument('json'); - $status = $this->twitterStatusArray($notice); - $this->showJsonObjects($status); - $this->endDocument('json'); - } - - function showXmlTimeline($notice) - { - $this->initDocument('xml'); - $this->elementStart('statuses', array('type' => 'array', - 'xmlns:statusnet' => 'http://status.net/schema/api/1/')); - - if (is_array($notice)) { - //FIXME: make everything calling showJsonTimeline use only Notice objects - $ids = array(); - foreach ($notice as $n) { - $ids[] = $n->getID(); - } - $notice = Notice::multiGet('id', $ids); - } - - while ($notice->fetch()) { - try { - $twitter_status = $this->twitterStatusArray($notice); - $this->showTwitterXmlStatus($twitter_status); - } catch (Exception $e) { - common_log(LOG_ERR, $e->getMessage()); - continue; - } - } - - $this->elementEnd('statuses'); - $this->endDocument('xml'); - } - - 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', - 'rel' => 'http://api.friendfeed.com/2008/03#sup', - 'href' => $suplink, - 'type' => 'application/json')); - } - - if (!is_null($logo)) { - $this->elementStart('image'); - $this->element('link', null, $link); - $this->element('title', null, $title); - $this->element('url', null, $logo); - $this->elementEnd('image'); - } - - $this->element('description', null, $subtitle); - $this->element('language', null, 'en-us'); - $this->element('ttl', null, '40'); - - if (is_array($notice)) { - //FIXME: make everything calling showJsonTimeline use only Notice objects - $ids = array(); - foreach ($notice as $n) { - $ids[] = $n->getID(); - } - $notice = Notice::multiGet('id', $ids); - } - - while ($notice->fetch()) { - try { - $entry = $this->twitterRssEntryArray($notice); - $this->showTwitterRssItem($entry); - } catch (Exception $e) { - common_log(LOG_ERR, $e->getMessage()); - // continue on exceptions - } - } - - $this->endTwitterRss(); - } - - function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null) + public function showAtomTimeline($notice, $title, $id, $link, $subtitle = null, $suplink = null, $selfuri = null, $logo = null) { $this->initDocument('atom'); @@ -901,14 +846,14 @@ class ApiAction extends Action $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); if (!is_null($logo)) { - $this->element('logo',null,$logo); + $this->element('logo', null, $logo); } if (!is_null($suplink)) { // For FriendFeed's SUP protocol $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', - 'href' => $suplink, - 'type' => 'application/json')); + 'href' => $suplink, + 'type' => 'application/json')); } if (!is_null($selfuri)) { @@ -940,7 +885,7 @@ class ApiAction extends Action $this->endDocument('atom'); } - function showRssGroups($group, $title, $link, $subtitle) + public function showRssGroups($group, $title, $link, $subtitle) { $this->initDocument('rss'); @@ -965,7 +910,25 @@ class ApiAction extends Action $this->endTwitterRss(); } - function showTwitterAtomEntry($entry) + public function twitterRssGroupArray($group) + { + $entry = array(); + $entry['content'] = $group->description; + $entry['title'] = $group->nickname; + $entry['link'] = $group->permalink(); + $entry['published'] = common_date_iso8601($group->created); + $entry['updated'] = common_date_iso8601($group->modified); + $taguribase = common_config('integration', 'groupuri'); + $entry['id'] = "group:$taguribase:$entry[link]"; + + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($group->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + public function showTwitterAtomEntry($entry) { $this->elementStart('entry'); $this->element('title', null, common_xml_safe_str($entry['title'])); @@ -978,11 +941,11 @@ class ApiAction extends Action $this->element('published', null, $entry['published']); $this->element('updated', null, $entry['updated']); $this->element('link', array('type' => 'text/html', - 'href' => $entry['link'], - 'rel' => 'alternate')); + 'href' => $entry['link'], + 'rel' => 'alternate')); $this->element('link', array('type' => $entry['avatar-type'], - 'href' => $entry['avatar'], - 'rel' => 'image')); + 'href' => $entry['avatar'], + 'rel' => 'image')); $this->elementStart('author'); $this->element('name', null, $entry['author-name']); @@ -992,7 +955,7 @@ class ApiAction extends Action $this->elementEnd('entry'); } - function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null) + public function showAtomGroups($group, $title, $id, $link, $subtitle = null, $selfuri = null) { $this->initDocument('atom'); @@ -1019,10 +982,9 @@ class ApiAction extends Action } $this->endDocument('atom'); - } - function showJsonTimeline($notice) + public function showJsonTimeline($notice) { $this->initDocument('json'); @@ -1052,7 +1014,7 @@ class ApiAction extends Action $this->endDocument('json'); } - function showJsonGroups($group) + public function showJsonGroups($group) { $this->initDocument('json'); @@ -1075,9 +1037,40 @@ class ApiAction extends Action $this->endDocument('json'); } - function showXmlGroups($group) + public function twitterGroupArray($group) { + $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 ($this->scoped instanceof Profile) { + $twitter_group['member'] = $this->scoped->isMember($group); + $twitter_group['blocked'] = Group_block::isBlocked( + $group, + $this->scoped + ); + } + + $twitter_group['admin_count'] = $group->getAdminCount(); + $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'] = self::dateTwitter($group->created); + $twitter_group['modified'] = self::dateTwitter($group->modified); + + return $twitter_group; + } + + public function showXmlGroups($group) + { $this->initDocument('xml'); $this->elementStart('groups', array('type' => 'array')); @@ -1097,9 +1090,17 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showXmlLists($list, $next_cursor=0, $prev_cursor=0) + public function showTwitterXmlGroup($twitter_group) { + $this->elementStart('group'); + foreach ($twitter_group as $element => $value) { + $this->element($element, null, $value); + } + $this->elementEnd('group'); + } + public function showXmlLists($list, $next_cursor = 0, $prev_cursor = 0) + { $this->initDocument('xml'); $this->elementStart('lists_list'); $this->elementStart('lists', array('type' => 'array')); @@ -1125,7 +1126,46 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showJsonLists($list, $next_cursor=0, $prev_cursor=0) + public function twitterListArray($list) + { + $profile = Profile::getKV('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 ($this->scoped instanceof Profile) { + $twitter_list['following'] = $list->hasSubscriber($this->scoped); + } else { + $twitter_list['following'] = false; + } + + $twitter_list['mode'] = ($list->private) ? 'private' : 'public'; + $twitter_list['user'] = $this->twitterUserArray($profile, false); + + return $twitter_list; + } + + public function showTwitterXmlList($twitter_list) + { + $this->elementStart('list'); + foreach ($twitter_list as $element => $value) { + if ($element == 'user') { + $this->showTwitterXmlUser($value, 'user'); + } else { + $this->element($element, null, $value); + } + } + $this->elementEnd('list'); + } + + public function showJsonLists($list, $next_cursor = 0, $prev_cursor = 0) { $this->initDocument('json'); @@ -1156,11 +1196,11 @@ class ApiAction extends Action $this->endDocument('json'); } - function showTwitterXmlUsers($user) + public function showTwitterXmlUsers($user) { $this->initDocument('xml'); $this->elementStart('users', array('type' => 'array', - 'xmlns:statusnet' => 'http://status.net/schema/api/1/')); + 'xmlns:statusnet' => 'http://status.net/schema/api/1/')); if (is_array($user)) { foreach ($user as $u) { @@ -1178,7 +1218,7 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showJsonUsers($user) + public function showJsonUsers($user) { $this->initDocument('json'); @@ -1201,7 +1241,7 @@ class ApiAction extends Action $this->endDocument('json'); } - function showSingleJsonGroup($group) + public function showSingleJsonGroup($group) { $this->initDocument('json'); $twitter_group = $this->twitterGroupArray($group); @@ -1209,7 +1249,7 @@ class ApiAction extends Action $this->endDocument('json'); } - function showSingleXmlGroup($group) + public function showSingleXmlGroup($group) { $this->initDocument('xml'); $twitter_group = $this->twitterGroupArray($group); @@ -1217,7 +1257,7 @@ class ApiAction extends Action $this->endDocument('xml'); } - function showSingleJsonList($list) + public function showSingleJsonList($list) { $this->initDocument('json'); $twitter_list = $this->twitterListArray($list); @@ -1225,7 +1265,7 @@ class ApiAction extends Action $this->endDocument('json'); } - function showSingleXmlList($list) + public function showSingleXmlList($list) { $this->initDocument('xml'); $twitter_list = $this->twitterListArray($list); @@ -1233,173 +1273,42 @@ class ApiAction extends Action $this->endDocument('xml'); } - static function dateTwitter($dt) - { - $dateStr = date('d F Y H:i:s', strtotime($dt)); - $d = new DateTime($dateStr, new DateTimeZone('UTC')); - $d->setTimezone(new DateTimeZone(common_timezone())); - return $d->format('D M d H:i:s O Y'); - } - - function initDocument($type='xml') - { - switch ($type) { - case 'xml': - header('Content-Type: application/xml; charset=utf-8'); - $this->startXML(); - break; - case 'json': - header('Content-Type: application/json; charset=utf-8'); - - // Check for JSONP callback - if (isset($this->callback)) { - print $this->callback . '('; - } - break; - case 'rss': - header("Content-Type: application/rss+xml; charset=utf-8"); - $this->initTwitterRss(); - break; - case 'atom': - header('Content-Type: application/atom+xml; charset=utf-8'); - $this->initTwitterAtom(); - break; - default: - // TRANS: Client error on an API request with an unsupported data format. - $this->clientError(_('Not a supported data format.')); - } - - return; - } - - function endDocument($type='xml') - { - switch ($type) { - case 'xml': - $this->endXML(); - break; - case 'json': - // Check for JSONP callback - if (isset($this->callback)) { - print ')'; - } - break; - case 'rss': - $this->endTwitterRss(); - break; - case 'atom': - $this->endTwitterRss(); - break; - default: - // TRANS: Client error on an API request with an unsupported data format. - $this->clientError(_('Not a supported data format.')); - } - return; - } - - function initTwitterRss() - { - $this->startXML(); - $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)); - } - - function endTwitterRss() - { - $this->elementEnd('channel'); - $this->elementEnd('rss'); - $this->endXML(); - } - - function initTwitterAtom() - { - $this->startXML(); - // FIXME: don't hardcode the language here! - $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', - 'xml:lang' => 'en-US', - 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); - } - - function endTwitterAtom() + public function endTwitterAtom() { $this->elementEnd('feed'); $this->endXML(); } - function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true) + public function showProfile($profile, $content_type = 'xml', $notice = null, $includeStatuses = true) { $profile_array = $this->twitterUserArray($profile, $includeStatuses); switch ($content_type) { - case 'xml': - $this->showTwitterXmlUser($profile_array); - break; - case 'json': - $this->showJsonObjects($profile_array); - break; - default: - // TRANS: Client error on an API request with an unsupported data format. - $this->clientError(_('Not a supported data format.')); + case 'xml': + $this->showTwitterXmlUser($profile_array); + break; + case 'json': + $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; } - 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 (self::is_decimal($this->arg('id'))) { - return User::getKV($this->arg('id')); - } else if ($this->arg('id')) { - $nickname = common_canonical_nickname($this->arg('id')); - return User::getKV('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 (self::is_decimal($this->arg('user_id'))) { - return User::getKV('id', $this->arg('user_id')); - } - } else if ($this->arg('screen_name')) { - $nickname = common_canonical_nickname($this->arg('screen_name')); - return User::getKV('nickname', $nickname); - } else { - // Fall back to trying the currently authenticated user - return $this->scoped->getUser(); - } - - } else if (self::is_decimal($id)) { - return User::getKV($id); - } else { - $nickname = common_canonical_nickname($id); - return User::getKV('nickname', $nickname); - } - } - - function getTargetProfile($id) + public function getTargetProfile($id) { if (empty($id)) { // Twitter supports these other ways of passing the user ID if (self::is_decimal($this->arg('id'))) { return Profile::getKV($this->arg('id')); - } else if ($this->arg('id')) { + } elseif ($this->arg('id')) { // Screen names currently can only uniquely identify a local user. $nickname = common_canonical_nickname($this->arg('id')); $user = User::getKV('nickname', $nickname); return $user ? $user->getProfile() : null; - } else if ($this->arg('user_id')) { + } elseif ($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 (self::is_decimal($this->arg('user_id'))) { @@ -1413,7 +1322,7 @@ class ApiAction extends Action // Fall back to trying the currently authenticated user return $this->scoped; } - } else if (self::is_decimal($id) && intval($id) > 0) { + } elseif (self::is_decimal($id) && intval($id) > 0) { return Profile::getByID($id); } else { // FIXME: check if isAcct to identify remote profiles and not just local nicknames @@ -1423,33 +1332,80 @@ class ApiAction extends Action } } - function getTargetGroup($id) + private static function is_decimal($str) + { + return preg_match('/^[0-9]+$/', $str); + } + + /** + * Returns query argument or default value if not found. Certain + * parameters used throughout the API are lightly scrubbed and + * bounds checked. This overrides Action::arg(). + * + * @param string $key requested argument + * @param string $def default value to return if $key is not provided + * + * @return var $var + */ + public function arg($key, $def = null) + { + // XXX: Do even more input validation/scrubbing? + + if (array_key_exists($key, $this->args)) { + switch ($key) { + case 'page': + $page = (int)$this->args['page']; + return ($page < 1) ? 1 : $page; + case 'count': + $count = (int)$this->args['count']; + if ($count < 1) { + return 20; + } elseif ($count > 200) { + return 200; + } else { + return $count; + } + // no break + case 'since_id': + $since_id = (int)$this->args['since_id']; + return ($since_id < 1) ? 0 : $since_id; + case 'max_id': + $max_id = (int)$this->args['max_id']; + return ($max_id < 1) ? 0 : $max_id; + default: + return parent::arg($key, $def); + } + } else { + return $def; + } + } + + public function getTargetGroup($id) { if (empty($id)) { if (self::is_decimal($this->arg('id'))) { return User_group::getKV('id', $this->arg('id')); - } else if ($this->arg('id')) { + } elseif ($this->arg('id')) { return User_group::getForNickname($this->arg('id')); - } else if ($this->arg('group_id')) { + } elseif ($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::getKV('id', $this->arg('group_id')); } - } else if ($this->arg('group_name')) { + } elseif ($this->arg('group_name')) { return User_group::getForNickname($this->arg('group_name')); } - - } else if (self::is_decimal($id)) { + } elseif (self::is_decimal($id)) { return User_group::getKV('id', $id); - } else if ($this->arg('uri')) { // FIXME: move this into empty($id) check? + } elseif ($this->arg('uri')) { // FIXME: move this into empty($id) check? return User_group::getKV('uri', urldecode($this->arg('uri'))); } else { return User_group::getForNickname($id); } } - function getTargetList($user=null, $id=null) + public function getTargetList($user = null, $id = null) { $tagger = $this->getTargetUser($user); $list = null; @@ -1458,12 +1414,12 @@ class ApiAction extends Action $id = $this->arg('id'); } - if($id) { + if ($id) { if (is_numeric($id)) { $list = Profile_list::getKV('id', $id); // only if the list with the id belongs to the tagger - if(empty($list) || $list->tagger != $tagger->id) { + if (empty($list) || $list->tagger != $tagger->id) { $list = null; } } @@ -1483,45 +1439,33 @@ class ApiAction extends Action return null; } - /** - * Returns query argument or default value if not found. Certain - * parameters used throughout the API are lightly scrubbed and - * bounds checked. This overrides Action::arg(). - * - * @param string $key requested argument - * @param string $def default value to return if $key is not provided - * - * @return var $var - */ - function arg($key, $def=null) + public function getTargetUser($id) { - // XXX: Do even more input validation/scrubbing? - - if (array_key_exists($key, $this->args)) { - switch($key) { - case 'page': - $page = (int)$this->args['page']; - return ($page < 1) ? 1 : $page; - case 'count': - $count = (int)$this->args['count']; - if ($count < 1) { - return 20; - } elseif ($count > 200) { - return 200; - } else { - return $count; + if (empty($id)) { + // Twitter supports these other ways of passing the user ID + if (self::is_decimal($this->arg('id'))) { + return User::getKV($this->arg('id')); + } elseif ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User::getKV('nickname', $nickname); + } elseif ($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 (self::is_decimal($this->arg('user_id'))) { + return User::getKV('id', $this->arg('user_id')); } - case 'since_id': - $since_id = (int)$this->args['since_id']; - return ($since_id < 1) ? 0 : $since_id; - case 'max_id': - $max_id = (int)$this->args['max_id']; - return ($max_id < 1) ? 0 : $max_id; - default: - return parent::arg($key, $def); + } elseif ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return User::getKV('nickname', $nickname); + } else { + // Fall back to trying the currently authenticated user + return $this->scoped->getUser(); } + } elseif (self::is_decimal($id)) { + return User::getKV($id); } else { - return $def; + $nickname = common_canonical_nickname($id); + return User::getKV('nickname', $nickname); } } @@ -1531,7 +1475,7 @@ class ApiAction extends Action * * @return string URL a URL suitable for rel="self" Atom links */ - function getSelfUri() + public function getSelfUri() { $action = mb_substr(get_class($this), 0, -6); // remove 'Action' @@ -1566,4 +1510,53 @@ class ApiAction extends Action return $uri; } + + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if user doesn't exist + */ + protected function prepare(array $args = array()) + { + GNUsocial::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); + $this->since_id = (int)$this->arg('since_id', 0); + + // These two are not used everywhere, mainly just AtompubAction extensions + $this->offset = ($this->page - 1) * $this->count; + $this->limit = $this->count + 1; + + if ($this->arg('since')) { + header('X-GNUsocial-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; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + protected function handle() + { + header('Access-Control-Allow-Origin: *'); + parent::handle(); + } }