]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/apiaction.php
Merge branch '1.0.x' into testing
[quix0rs-gnu-social.git] / lib / apiaction.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Base API action
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  API
23  * @package   StatusNet
24  * @author    Craig Andrews <candrews@integralblue.com>
25  * @author    Dan Moore <dan@moore.cx>
26  * @author    Evan Prodromou <evan@status.net>
27  * @author    Jeffery To <jeffery.to@gmail.com>
28  * @author    Toby Inkster <mail@tobyinkster.co.uk>
29  * @author    Zach Copley <zach@status.net>
30  * @copyright 2009-2010 StatusNet, Inc.
31  * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
32  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
33  * @link      http://status.net/
34  */
35
36 /* External API usage documentation. Please update when you change how the API works. */
37
38 /*! @mainpage StatusNet REST API
39
40     @section Introduction
41
42     Some explanatory text about the API would be nice.
43
44     @section API Methods
45
46     @subsection timelinesmethods_sec Timeline Methods
47
48     @li @ref publictimeline
49     @li @ref friendstimeline
50
51     @subsection statusmethods_sec Status Methods
52
53     @li @ref statusesupdate
54
55     @subsection usermethods_sec User Methods
56
57     @subsection directmessagemethods_sec Direct Message Methods
58
59     @subsection friendshipmethods_sec Friendship Methods
60
61     @subsection socialgraphmethods_sec Social Graph Methods
62
63     @subsection accountmethods_sec Account Methods
64
65     @subsection favoritesmethods_sec Favorites Methods
66
67     @subsection blockmethods_sec Block Methods
68
69     @subsection oauthmethods_sec OAuth Methods
70
71     @subsection helpmethods_sec Help Methods
72
73     @subsection groupmethods_sec Group Methods
74
75     @page apiroot API Root
76
77     The URLs for methods referred to in this API documentation are
78     relative to the StatusNet API root. The API root is determined by the
79     site's @b server and @b path variables, which are generally specified
80     in config.php. For example:
81
82     @code
83     $config['site']['server'] = 'example.org';
84     $config['site']['path'] = 'statusnet'
85     @endcode
86
87     The pattern for a site's API root is: @c protocol://server/path/api E.g:
88
89     @c http://example.org/statusnet/api
90
91     The @b path can be empty.  In that case the API root would simply be:
92
93     @c http://example.org/api
94
95 */
96
97 if (!defined('STATUSNET')) {
98     exit(1);
99 }
100
101 class ApiValidationException extends Exception { }
102
103 /**
104  * Contains most of the Twitter-compatible API output functions.
105  *
106  * @category API
107  * @package  StatusNet
108  * @author   Craig Andrews <candrews@integralblue.com>
109  * @author   Dan Moore <dan@moore.cx>
110  * @author   Evan Prodromou <evan@status.net>
111  * @author   Jeffery To <jeffery.to@gmail.com>
112  * @author   Toby Inkster <mail@tobyinkster.co.uk>
113  * @author   Zach Copley <zach@status.net>
114  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
115  * @link     http://status.net/
116  */
117 class ApiAction extends Action
118 {
119     const READ_ONLY  = 1;
120     const READ_WRITE = 2;
121
122     var $format    = null;
123     var $user      = null;
124     var $auth_user = null;
125     var $page      = null;
126     var $count     = null;
127     var $max_id    = null;
128     var $since_id  = null;
129     var $source    = null;
130     var $callback  = null;
131
132     var $access    = self::READ_ONLY;  // read (default) or read-write
133
134     static $reserved_sources = array('web', 'omb', 'ostatus', 'mail', 'xmpp', 'api');
135
136     /**
137      * Initialization.
138      *
139      * @param array $args Web and URL arguments
140      *
141      * @return boolean false if user doesn't exist
142      */
143     function prepare($args)
144     {
145         StatusNet::setApi(true); // reduce exception reports to aid in debugging
146         parent::prepare($args);
147
148         $this->format   = $this->arg('format');
149         $this->callback = $this->arg('callback');
150         $this->page     = (int)$this->arg('page', 1);
151         $this->count    = (int)$this->arg('count', 20);
152         $this->max_id   = (int)$this->arg('max_id', 0);
153         $this->since_id = (int)$this->arg('since_id', 0);
154
155         if ($this->arg('since')) {
156             header('X-StatusNet-Warning: since parameter is disabled; use since_id');
157         }
158
159         $this->source = $this->trimmed('source');
160
161         if (empty($this->source) || in_array($this->source, self::$reserved_sources)) {
162             $this->source = 'api';
163         }
164
165         return true;
166     }
167
168     /**
169      * Handle a request
170      *
171      * @param array $args Arguments from $_REQUEST
172      *
173      * @return void
174      */
175     function handle($args)
176     {
177         header('Access-Control-Allow-Origin: *');
178         parent::handle($args);
179     }
180
181     /**
182      * Overrides XMLOutputter::element to write booleans as strings (true|false).
183      * See that method's documentation for more info.
184      *
185      * @param string $tag     Element type or tagname
186      * @param array  $attrs   Array of element attributes, as
187      *                        key-value pairs
188      * @param string $content string content of the element
189      *
190      * @return void
191      */
192     function element($tag, $attrs=null, $content=null)
193     {
194         if (is_bool($content)) {
195             $content = ($content ? 'true' : 'false');
196         }
197
198         return parent::element($tag, $attrs, $content);
199     }
200
201     function twitterUserArray($profile, $get_notice=false)
202     {
203         $twitter_user = array();
204
205         $user = $profile->getUser();
206
207         $twitter_user['id'] = intval($profile->id);
208         $twitter_user['name'] = $profile->getBestName();
209         $twitter_user['screen_name'] = $profile->nickname;
210         $twitter_user['location'] = ($profile->location) ? $profile->location : null;
211         $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
212
213         $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
214         $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
215             Avatar::defaultImage(AVATAR_STREAM_SIZE);
216
217         $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
218         $twitter_user['protected'] = ($user->private_stream) ? true : false;
219         $twitter_user['followers_count'] = $profile->subscriberCount();
220
221         $design = null;
222
223         // Note: some profiles don't have an associated user
224
225         $defaultDesign = Design::siteDesign();
226
227         if (!empty($user)) {
228             $design = $user->getDesign();
229         }
230
231         if (empty($design)) {
232             $design = $defaultDesign;
233         }
234
235         $color = Design::toWebColor(empty($design->backgroundcolor) ? $defaultDesign->backgroundcolor : $design->backgroundcolor);
236         $twitter_user['profile_background_color'] = ($color == null) ? '' : '#'.$color->hexValue();
237         $color = Design::toWebColor(empty($design->textcolor) ? $defaultDesign->textcolor : $design->textcolor);
238         $twitter_user['profile_text_color'] = ($color == null) ? '' : '#'.$color->hexValue();
239         $color = Design::toWebColor(empty($design->linkcolor) ? $defaultDesign->linkcolor : $design->linkcolor);
240         $twitter_user['profile_link_color'] = ($color == null) ? '' : '#'.$color->hexValue();
241         $color = Design::toWebColor(empty($design->sidebarcolor) ? $defaultDesign->sidebarcolor : $design->sidebarcolor);
242         $twitter_user['profile_sidebar_fill_color'] = ($color == null) ? '' : '#'.$color->hexValue();
243         $twitter_user['profile_sidebar_border_color'] = '';
244
245         $twitter_user['friends_count'] = $profile->subscriptionCount();
246
247         $twitter_user['created_at'] = $this->dateTwitter($profile->created);
248
249         $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
250
251         $timezone = 'UTC';
252
253         if (!empty($user) && $user->timezone) {
254             $timezone = $user->timezone;
255         }
256
257         $t = new DateTime;
258         $t->setTimezone(new DateTimeZone($timezone));
259
260         $twitter_user['utc_offset'] = $t->format('Z');
261         $twitter_user['time_zone'] = $timezone;
262
263         $twitter_user['profile_background_image_url']
264             = empty($design->backgroundimage)
265             ? '' : ($design->disposition & BACKGROUND_ON)
266             ? Design::url($design->backgroundimage) : '';
267
268         $twitter_user['profile_background_tile']
269             = (bool)($design->disposition & BACKGROUND_TILE);
270
271         $twitter_user['statuses_count'] = $profile->noticeCount();
272
273         // Is the requesting user following this user?
274         $twitter_user['following'] = false;
275         $twitter_user['statusnet:blocking'] = false;
276         $twitter_user['notifications'] = false;
277
278         if (isset($this->auth_user)) {
279
280             $twitter_user['following'] = $this->auth_user->isSubscribed($profile);
281             $twitter_user['statusnet:blocking']  = $this->auth_user->hasBlocked($profile);
282
283             // Notifications on?
284             $sub = Subscription::pkeyGet(array('subscriber' =>
285                                                $this->auth_user->id,
286                                                'subscribed' => $profile->id));
287
288             if ($sub) {
289                 $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
290             }
291         }
292
293         if ($get_notice) {
294             $notice = $profile->getCurrentNotice();
295             if ($notice) {
296                 // don't get user!
297                 $twitter_user['status'] = $this->twitterStatusArray($notice, false);
298             }
299         }
300
301         // StatusNet-specific
302
303         $twitter_user['statusnet_profile_url'] = $profile->profileurl;
304
305         return $twitter_user;
306     }
307
308     function twitterStatusArray($notice, $include_user=true)
309     {
310         $base = $this->twitterSimpleStatusArray($notice, $include_user);
311
312         if (!empty($notice->repeat_of)) {
313             $original = Notice::staticGet('id', $notice->repeat_of);
314             if (!empty($original)) {
315                 $original_array = $this->twitterSimpleStatusArray($original, $include_user);
316                 $base['retweeted_status'] = $original_array;
317             }
318         }
319
320         return $base;
321     }
322
323     function twitterSimpleStatusArray($notice, $include_user=true)
324     {
325         $profile = $notice->getProfile();
326
327         $twitter_status = array();
328         $twitter_status['text'] = $notice->content;
329         $twitter_status['truncated'] = false; # Not possible on StatusNet
330         $twitter_status['created_at'] = $this->dateTwitter($notice->created);
331         $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
332             intval($notice->reply_to) : null;
333
334         $source = null;
335
336         $ns = $notice->getSource();
337         if ($ns) {
338             if (!empty($ns->name) && !empty($ns->url)) {
339                 $source = '<a href="'
340                     . htmlspecialchars($ns->url)
341                     . '" rel="nofollow">'
342                     . htmlspecialchars($ns->name)
343                     . '</a>';
344             } else {
345                 $source = $ns->code;
346             }
347         }
348
349         $twitter_status['source'] = $source;
350         $twitter_status['id'] = intval($notice->id);
351
352         $replier_profile = null;
353
354         if ($notice->reply_to) {
355             $reply = Notice::staticGet(intval($notice->reply_to));
356             if ($reply) {
357                 $replier_profile = $reply->getProfile();
358             }
359         }
360
361         $twitter_status['in_reply_to_user_id'] =
362             ($replier_profile) ? intval($replier_profile->id) : null;
363         $twitter_status['in_reply_to_screen_name'] =
364             ($replier_profile) ? $replier_profile->nickname : null;
365
366         if (isset($notice->lat) && isset($notice->lon)) {
367             // This is the format that GeoJSON expects stuff to be in
368             $twitter_status['geo'] = array('type' => 'Point',
369                                            'coordinates' => array((float) $notice->lat,
370                                                                   (float) $notice->lon));
371         } else {
372             $twitter_status['geo'] = null;
373         }
374
375         if (isset($this->auth_user)) {
376             $twitter_status['favorited'] = $this->auth_user->hasFave($notice);
377         } else {
378             $twitter_status['favorited'] = false;
379         }
380
381         // Enclosures
382         $attachments = $notice->attachments();
383
384         if (!empty($attachments)) {
385
386             $twitter_status['attachments'] = array();
387
388             foreach ($attachments as $attachment) {
389                 $enclosure_o=$attachment->getEnclosure();
390                 if ($enclosure_o) {
391                     $enclosure = array();
392                     $enclosure['url'] = $enclosure_o->url;
393                     $enclosure['mimetype'] = $enclosure_o->mimetype;
394                     $enclosure['size'] = $enclosure_o->size;
395                     $twitter_status['attachments'][] = $enclosure;
396                 }
397             }
398         }
399
400         if ($include_user && $profile) {
401             // Don't get notice (recursive!)
402             $twitter_user = $this->twitterUserArray($profile, false);
403             $twitter_status['user'] = $twitter_user;
404         }
405
406         // StatusNet-specific
407
408         $twitter_status['statusnet_html'] = $notice->rendered;
409
410         return $twitter_status;
411     }
412
413     function twitterGroupArray($group)
414     {
415         $twitter_group = array();
416
417         $twitter_group['id'] = intval($group->id);
418         $twitter_group['url'] = $group->permalink();
419         $twitter_group['nickname'] = $group->nickname;
420         $twitter_group['fullname'] = $group->fullname;
421
422         if (isset($this->auth_user)) {
423             $twitter_group['member'] = $this->auth_user->isMember($group);
424             $twitter_group['blocked'] = Group_block::isBlocked(
425                 $group,
426                 $this->auth_user->getProfile()
427             );
428         }
429
430         $twitter_group['member_count'] = $group->getMemberCount();
431         $twitter_group['original_logo'] = $group->original_logo;
432         $twitter_group['homepage_logo'] = $group->homepage_logo;
433         $twitter_group['stream_logo'] = $group->stream_logo;
434         $twitter_group['mini_logo'] = $group->mini_logo;
435         $twitter_group['homepage'] = $group->homepage;
436         $twitter_group['description'] = $group->description;
437         $twitter_group['location'] = $group->location;
438         $twitter_group['created'] = $this->dateTwitter($group->created);
439         $twitter_group['modified'] = $this->dateTwitter($group->modified);
440
441         return $twitter_group;
442     }
443
444     function twitterRssGroupArray($group)
445     {
446         $entry = array();
447         $entry['content']=$group->description;
448         $entry['title']=$group->nickname;
449         $entry['link']=$group->permalink();
450         $entry['published']=common_date_iso8601($group->created);
451         $entry['updated']==common_date_iso8601($group->modified);
452         $taguribase = common_config('integration', 'groupuri');
453         $entry['id'] = "group:$groupuribase:$entry[link]";
454
455         $entry['description'] = $entry['content'];
456         $entry['pubDate'] = common_date_rfc2822($group->created);
457         $entry['guid'] = $entry['link'];
458
459         return $entry;
460     }
461
462     function twitterListArray($list)
463     {
464         $profile = Profile::staticGet('id', $list->tagger);
465
466         $twitter_list = array();
467         $twitter_list['id'] = $list->id;
468         $twitter_list['name'] = $list->tag;
469         $twitter_list['full_name'] = '@'.$profile->nickname.'/'.$list->tag;;
470         $twitter_list['slug'] = $list->tag;
471         $twitter_list['description'] = $list->description;
472         $twitter_list['subscriber_count'] = $list->subscriberCount();
473         $twitter_list['member_count'] = $list->taggedCount();
474         $twitter_list['uri'] = $list->getUri();
475
476         if (isset($this->auth_user)) {
477             $twitter_list['following'] = $list->hasSubscriber($this->auth_user);
478         } else {
479             $twitter_list['following'] = false;
480         }
481
482         $twitter_list['mode'] = ($list->private) ? 'private' : 'public';
483         $twitter_list['user'] = $this->twitterUserArray($profile, false);
484
485         return $twitter_list;
486     }
487
488     function twitterRssEntryArray($notice)
489     {
490         $entry = array();
491
492         if (Event::handle('StartRssEntryArray', array($notice, &$entry))) {
493             $profile = $notice->getProfile();
494
495             // We trim() to avoid extraneous whitespace in the output
496
497             $entry['content'] = common_xml_safe_str(trim($notice->rendered));
498             $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
499             $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
500             $entry['published'] = common_date_iso8601($notice->created);
501
502             $taguribase = TagURI::base();
503             $entry['id'] = "tag:$taguribase:$entry[link]";
504
505             $entry['updated'] = $entry['published'];
506             $entry['author'] = $profile->getBestName();
507
508             // Enclosures
509             $attachments = $notice->attachments();
510             $enclosures = array();
511
512             foreach ($attachments as $attachment) {
513                 $enclosure_o=$attachment->getEnclosure();
514                 if ($enclosure_o) {
515                     $enclosure = array();
516                     $enclosure['url'] = $enclosure_o->url;
517                     $enclosure['mimetype'] = $enclosure_o->mimetype;
518                     $enclosure['size'] = $enclosure_o->size;
519                     $enclosures[] = $enclosure;
520                 }
521             }
522
523             if (!empty($enclosures)) {
524                 $entry['enclosures'] = $enclosures;
525             }
526
527             // Tags/Categories
528             $tag = new Notice_tag();
529             $tag->notice_id = $notice->id;
530             if ($tag->find()) {
531                 $entry['tags']=array();
532                 while ($tag->fetch()) {
533                     $entry['tags'][]=$tag->tag;
534                 }
535             }
536             $tag->free();
537
538             // RSS Item specific
539             $entry['description'] = $entry['content'];
540             $entry['pubDate'] = common_date_rfc2822($notice->created);
541             $entry['guid'] = $entry['link'];
542
543             if (isset($notice->lat) && isset($notice->lon)) {
544                 // This is the format that GeoJSON expects stuff to be in.
545                 // showGeoRSS() below uses it for XML output, so we reuse it
546                 $entry['geo'] = array('type' => 'Point',
547                                       'coordinates' => array((float) $notice->lat,
548                                                              (float) $notice->lon));
549             } else {
550                 $entry['geo'] = null;
551             }
552
553             Event::handle('EndRssEntryArray', array($notice, &$entry));
554         }
555
556         return $entry;
557     }
558
559     function twitterRelationshipArray($source, $target)
560     {
561         $relationship = array();
562
563         $relationship['source'] =
564             $this->relationshipDetailsArray($source, $target);
565         $relationship['target'] =
566             $this->relationshipDetailsArray($target, $source);
567
568         return array('relationship' => $relationship);
569     }
570
571     function relationshipDetailsArray($source, $target)
572     {
573         $details = array();
574
575         $details['screen_name'] = $source->nickname;
576         $details['followed_by'] = $target->isSubscribed($source);
577         $details['following'] = $source->isSubscribed($target);
578
579         $notifications = false;
580
581         if ($source->isSubscribed($target)) {
582             $sub = Subscription::pkeyGet(array('subscriber' =>
583                 $source->id, 'subscribed' => $target->id));
584
585             if (!empty($sub)) {
586                 $notifications = ($sub->jabber || $sub->sms);
587             }
588         }
589
590         $details['notifications_enabled'] = $notifications;
591         $details['blocking'] = $source->hasBlocked($target);
592         $details['id'] = intval($source->id);
593
594         return $details;
595     }
596
597     function showTwitterXmlRelationship($relationship)
598     {
599         $this->elementStart('relationship');
600
601         foreach($relationship as $element => $value) {
602             if ($element == 'source' || $element == 'target') {
603                 $this->elementStart($element);
604                 $this->showXmlRelationshipDetails($value);
605                 $this->elementEnd($element);
606             }
607         }
608
609         $this->elementEnd('relationship');
610     }
611
612     function showXmlRelationshipDetails($details)
613     {
614         foreach($details as $element => $value) {
615             $this->element($element, null, $value);
616         }
617     }
618
619     function showTwitterXmlStatus($twitter_status, $tag='status', $namespaces=false)
620     {
621         $attrs = array();
622         if ($namespaces) {
623             $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
624         }
625         $this->elementStart($tag, $attrs);
626         foreach($twitter_status as $element => $value) {
627             switch ($element) {
628             case 'user':
629                 $this->showTwitterXmlUser($twitter_status['user']);
630                 break;
631             case 'text':
632                 $this->element($element, null, common_xml_safe_str($value));
633                 break;
634             case 'attachments':
635                 $this->showXmlAttachments($twitter_status['attachments']);
636                 break;
637             case 'geo':
638                 $this->showGeoXML($value);
639                 break;
640             case 'retweeted_status':
641                 $this->showTwitterXmlStatus($value, 'retweeted_status');
642                 break;
643             default:
644                 if (strncmp($element, 'statusnet_', 10) == 0) {
645                     $this->element('statusnet:'.substr($element, 10), null, $value);
646                 } else {
647                     $this->element($element, null, $value);
648                 }
649             }
650         }
651         $this->elementEnd($tag);
652     }
653
654     function showTwitterXmlGroup($twitter_group)
655     {
656         $this->elementStart('group');
657         foreach($twitter_group as $element => $value) {
658             $this->element($element, null, $value);
659         }
660         $this->elementEnd('group');
661     }
662
663     function showTwitterXmlList($twitter_list)
664     {
665         $this->elementStart('list');
666         foreach($twitter_list as $element => $value) {
667             if($element == 'user') {
668                 $this->showTwitterXmlUser($value, 'user');
669             }
670             else {
671                 $this->element($element, null, $value);
672             }
673         }
674         $this->elementEnd('list');
675     }
676
677     function showTwitterXmlUser($twitter_user, $role='user', $namespaces=false)
678     {
679         $attrs = array();
680         if ($namespaces) {
681             $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
682         }
683         $this->elementStart($role, $attrs);
684         foreach($twitter_user as $element => $value) {
685             if ($element == 'status') {
686                 $this->showTwitterXmlStatus($twitter_user['status']);
687             } else if (strncmp($element, 'statusnet_', 10) == 0) {
688                 $this->element('statusnet:'.substr($element, 10), null, $value);
689             } else {
690                 $this->element($element, null, $value);
691             }
692         }
693         $this->elementEnd($role);
694     }
695
696     function showXmlAttachments($attachments) {
697         if (!empty($attachments)) {
698             $this->elementStart('attachments', array('type' => 'array'));
699             foreach ($attachments as $attachment) {
700                 $attrs = array();
701                 $attrs['url'] = $attachment['url'];
702                 $attrs['mimetype'] = $attachment['mimetype'];
703                 $attrs['size'] = $attachment['size'];
704                 $this->element('enclosure', $attrs, '');
705             }
706             $this->elementEnd('attachments');
707         }
708     }
709
710     function showGeoXML($geo)
711     {
712         if (empty($geo)) {
713             // empty geo element
714             $this->element('geo');
715         } else {
716             $this->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss'));
717             $this->element('georss:point', null, $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]);
718             $this->elementEnd('geo');
719         }
720     }
721
722     function showGeoRSS($geo)
723     {
724         if (!empty($geo)) {
725             $this->element(
726                 'georss:point',
727                 null,
728                 $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]
729             );
730         }
731     }
732
733     function showTwitterRssItem($entry)
734     {
735         $this->elementStart('item');
736         $this->element('title', null, $entry['title']);
737         $this->element('description', null, $entry['description']);
738         $this->element('pubDate', null, $entry['pubDate']);
739         $this->element('guid', null, $entry['guid']);
740         $this->element('link', null, $entry['link']);
741
742         // RSS only supports 1 enclosure per item
743         if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
744             $enclosure = $entry['enclosures'][0];
745             $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
746         }
747
748         if(array_key_exists('tags', $entry)){
749             foreach($entry['tags'] as $tag){
750                 $this->element('category', null,$tag);
751             }
752         }
753
754         $this->showGeoRSS($entry['geo']);
755         $this->elementEnd('item');
756     }
757
758     function showJsonObjects($objects)
759     {
760         print(json_encode($objects));
761     }
762
763     function showSingleXmlStatus($notice)
764     {
765         $this->initDocument('xml');
766         $twitter_status = $this->twitterStatusArray($notice);
767         $this->showTwitterXmlStatus($twitter_status, 'status', true);
768         $this->endDocument('xml');
769     }
770
771     function showSingleAtomStatus($notice)
772     {
773         header('Content-Type: application/atom+xml; charset=utf-8');
774         print $notice->asAtomEntry(true, true, true, $this->auth_user);
775     }
776
777     function show_single_json_status($notice)
778     {
779         $this->initDocument('json');
780         $status = $this->twitterStatusArray($notice);
781         $this->showJsonObjects($status);
782         $this->endDocument('json');
783     }
784
785     function showXmlTimeline($notice)
786     {
787         $this->initDocument('xml');
788         $this->elementStart('statuses', array('type' => 'array',
789                                               'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
790
791         if (is_array($notice)) {
792             $notice = new ArrayWrapper($notice);
793         }
794
795         while ($notice->fetch()) {
796             try {
797                 $twitter_status = $this->twitterStatusArray($notice);
798                 $this->showTwitterXmlStatus($twitter_status);
799             } catch (Exception $e) {
800                 common_log(LOG_ERR, $e->getMessage());
801                 continue;
802             }
803         }
804
805         $this->elementEnd('statuses');
806         $this->endDocument('xml');
807     }
808
809     function showRssTimeline($notice, $title, $link, $subtitle, $suplink = null, $logo = null, $self = null)
810     {
811         $this->initDocument('rss');
812
813         $this->element('title', null, $title);
814         $this->element('link', null, $link);
815
816         if (!is_null($self)) {
817             $this->element(
818                 'atom:link',
819                 array(
820                     'type' => 'application/rss+xml',
821                     'href' => $self,
822                     'rel'  => 'self'
823                 )
824            );
825         }
826
827         if (!is_null($suplink)) {
828             // For FriendFeed's SUP protocol
829             $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
830                                          'rel' => 'http://api.friendfeed.com/2008/03#sup',
831                                          'href' => $suplink,
832                                          'type' => 'application/json'));
833         }
834
835         if (!is_null($logo)) {
836             $this->elementStart('image');
837             $this->element('link', null, $link);
838             $this->element('title', null, $title);
839             $this->element('url', null, $logo);
840             $this->elementEnd('image');
841         }
842
843         $this->element('description', null, $subtitle);
844         $this->element('language', null, 'en-us');
845         $this->element('ttl', null, '40');
846
847         if (is_array($notice)) {
848             $notice = new ArrayWrapper($notice);
849         }
850
851         while ($notice->fetch()) {
852             try {
853                 $entry = $this->twitterRssEntryArray($notice);
854                 $this->showTwitterRssItem($entry);
855             } catch (Exception $e) {
856                 common_log(LOG_ERR, $e->getMessage());
857                 // continue on exceptions
858             }
859         }
860
861         $this->endTwitterRss();
862     }
863
864     function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null)
865     {
866         $this->initDocument('atom');
867
868         $this->element('title', null, $title);
869         $this->element('id', null, $id);
870         $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
871
872         if (!is_null($logo)) {
873             $this->element('logo',null,$logo);
874         }
875
876         if (!is_null($suplink)) {
877             // For FriendFeed's SUP protocol
878             $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
879                                          'href' => $suplink,
880                                          'type' => 'application/json'));
881         }
882
883         if (!is_null($selfuri)) {
884             $this->element('link', array('href' => $selfuri,
885                 'rel' => 'self', 'type' => 'application/atom+xml'), null);
886         }
887
888         $this->element('updated', null, common_date_iso8601('now'));
889         $this->element('subtitle', null, $subtitle);
890
891         if (is_array($notice)) {
892             $notice = new ArrayWrapper($notice);
893         }
894
895         while ($notice->fetch()) {
896             try {
897                 $this->raw($notice->asAtomEntry());
898             } catch (Exception $e) {
899                 common_log(LOG_ERR, $e->getMessage());
900                 continue;
901             }
902         }
903
904         $this->endDocument('atom');
905     }
906
907     function showRssGroups($group, $title, $link, $subtitle)
908     {
909         $this->initDocument('rss');
910
911         $this->element('title', null, $title);
912         $this->element('link', null, $link);
913         $this->element('description', null, $subtitle);
914         $this->element('language', null, 'en-us');
915         $this->element('ttl', null, '40');
916
917         if (is_array($group)) {
918             foreach ($group as $g) {
919                 $twitter_group = $this->twitterRssGroupArray($g);
920                 $this->showTwitterRssItem($twitter_group);
921             }
922         } else {
923             while ($group->fetch()) {
924                 $twitter_group = $this->twitterRssGroupArray($group);
925                 $this->showTwitterRssItem($twitter_group);
926             }
927         }
928
929         $this->endTwitterRss();
930     }
931
932     function showTwitterAtomEntry($entry)
933     {
934         $this->elementStart('entry');
935         $this->element('title', null, common_xml_safe_str($entry['title']));
936         $this->element(
937             'content',
938             array('type' => 'html'),
939             common_xml_safe_str($entry['content'])
940         );
941         $this->element('id', null, $entry['id']);
942         $this->element('published', null, $entry['published']);
943         $this->element('updated', null, $entry['updated']);
944         $this->element('link', array('type' => 'text/html',
945                                      'href' => $entry['link'],
946                                      'rel' => 'alternate'));
947         $this->element('link', array('type' => $entry['avatar-type'],
948                                      'href' => $entry['avatar'],
949                                      'rel' => 'image'));
950         $this->elementStart('author');
951
952         $this->element('name', null, $entry['author-name']);
953         $this->element('uri', null, $entry['author-uri']);
954
955         $this->elementEnd('author');
956         $this->elementEnd('entry');
957     }
958
959     function showXmlDirectMessage($dm, $namespaces=false)
960     {
961         $attrs = array();
962         if ($namespaces) {
963             $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
964         }
965         $this->elementStart('direct_message', $attrs);
966         foreach($dm as $element => $value) {
967             switch ($element) {
968             case 'sender':
969             case 'recipient':
970                 $this->showTwitterXmlUser($value, $element);
971                 break;
972             case 'text':
973                 $this->element($element, null, common_xml_safe_str($value));
974                 break;
975             default:
976                 $this->element($element, null, $value);
977                 break;
978             }
979         }
980         $this->elementEnd('direct_message');
981     }
982
983     function directMessageArray($message)
984     {
985         $dmsg = array();
986
987         $from_profile = $message->getFrom();
988         $to_profile = $message->getTo();
989
990         $dmsg['id'] = intval($message->id);
991         $dmsg['sender_id'] = intval($from_profile);
992         $dmsg['text'] = trim($message->content);
993         $dmsg['recipient_id'] = intval($to_profile);
994         $dmsg['created_at'] = $this->dateTwitter($message->created);
995         $dmsg['sender_screen_name'] = $from_profile->nickname;
996         $dmsg['recipient_screen_name'] = $to_profile->nickname;
997         $dmsg['sender'] = $this->twitterUserArray($from_profile, false);
998         $dmsg['recipient'] = $this->twitterUserArray($to_profile, false);
999
1000         return $dmsg;
1001     }
1002
1003     function rssDirectMessageArray($message)
1004     {
1005         $entry = array();
1006
1007         $from = $message->getFrom();
1008
1009         $entry['title'] = sprintf('Message from %1$s to %2$s',
1010             $from->nickname, $message->getTo()->nickname);
1011
1012         $entry['content'] = common_xml_safe_str($message->rendered);
1013         $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
1014         $entry['published'] = common_date_iso8601($message->created);
1015
1016         $taguribase = TagURI::base();
1017
1018         $entry['id'] = "tag:$taguribase:$entry[link]";
1019         $entry['updated'] = $entry['published'];
1020
1021         $entry['author-name'] = $from->getBestName();
1022         $entry['author-uri'] = $from->homepage;
1023
1024         $avatar = $from->getAvatar(AVATAR_STREAM_SIZE);
1025
1026         $entry['avatar']      = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE);
1027         $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png';
1028
1029         // RSS item specific
1030
1031         $entry['description'] = $entry['content'];
1032         $entry['pubDate'] = common_date_rfc2822($message->created);
1033         $entry['guid'] = $entry['link'];
1034
1035         return $entry;
1036     }
1037
1038     function showSingleXmlDirectMessage($message)
1039     {
1040         $this->initDocument('xml');
1041         $dmsg = $this->directMessageArray($message);
1042         $this->showXmlDirectMessage($dmsg, true);
1043         $this->endDocument('xml');
1044     }
1045
1046     function showSingleJsonDirectMessage($message)
1047     {
1048         $this->initDocument('json');
1049         $dmsg = $this->directMessageArray($message);
1050         $this->showJsonObjects($dmsg);
1051         $this->endDocument('json');
1052     }
1053
1054     function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
1055     {
1056         $this->initDocument('atom');
1057
1058         $this->element('title', null, common_xml_safe_str($title));
1059         $this->element('id', null, $id);
1060         $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
1061
1062         if (!is_null($selfuri)) {
1063             $this->element('link', array('href' => $selfuri,
1064                 'rel' => 'self', 'type' => 'application/atom+xml'), null);
1065         }
1066
1067         $this->element('updated', null, common_date_iso8601('now'));
1068         $this->element('subtitle', null, common_xml_safe_str($subtitle));
1069
1070         if (is_array($group)) {
1071             foreach ($group as $g) {
1072                 $this->raw($g->asAtomEntry());
1073             }
1074         } else {
1075             while ($group->fetch()) {
1076                 $this->raw($group->asAtomEntry());
1077             }
1078         }
1079
1080         $this->endDocument('atom');
1081
1082     }
1083
1084     function showJsonTimeline($notice)
1085     {
1086         $this->initDocument('json');
1087
1088         $statuses = array();
1089
1090         if (is_array($notice)) {
1091             $notice = new ArrayWrapper($notice);
1092         }
1093
1094         while ($notice->fetch()) {
1095             try {
1096                 $twitter_status = $this->twitterStatusArray($notice);
1097                 array_push($statuses, $twitter_status);
1098             } catch (Exception $e) {
1099                 common_log(LOG_ERR, $e->getMessage());
1100                 continue;
1101             }
1102         }
1103
1104         $this->showJsonObjects($statuses);
1105
1106         $this->endDocument('json');
1107     }
1108
1109     function showJsonGroups($group)
1110     {
1111         $this->initDocument('json');
1112
1113         $groups = array();
1114
1115         if (is_array($group)) {
1116             foreach ($group as $g) {
1117                 $twitter_group = $this->twitterGroupArray($g);
1118                 array_push($groups, $twitter_group);
1119             }
1120         } else {
1121             while ($group->fetch()) {
1122                 $twitter_group = $this->twitterGroupArray($group);
1123                 array_push($groups, $twitter_group);
1124             }
1125         }
1126
1127         $this->showJsonObjects($groups);
1128
1129         $this->endDocument('json');
1130     }
1131
1132     function showXmlGroups($group)
1133     {
1134
1135         $this->initDocument('xml');
1136         $this->elementStart('groups', array('type' => 'array'));
1137
1138         if (is_array($group)) {
1139             foreach ($group as $g) {
1140                 $twitter_group = $this->twitterGroupArray($g);
1141                 $this->showTwitterXmlGroup($twitter_group);
1142             }
1143         } else {
1144             while ($group->fetch()) {
1145                 $twitter_group = $this->twitterGroupArray($group);
1146                 $this->showTwitterXmlGroup($twitter_group);
1147             }
1148         }
1149
1150         $this->elementEnd('groups');
1151         $this->endDocument('xml');
1152     }
1153
1154     function showXmlLists($list, $next_cursor=0, $prev_cursor=0)
1155     {
1156
1157         $this->initDocument('xml');
1158         $this->elementStart('lists_list');
1159         $this->elementStart('lists', array('type' => 'array'));
1160
1161         if (is_array($list)) {
1162             foreach ($list as $l) {
1163                 $twitter_list = $this->twitterListArray($l);
1164                 $this->showTwitterXmlList($twitter_list);
1165             }
1166         } else {
1167             while ($list->fetch()) {
1168                 $twitter_list = $this->twitterListArray($list);
1169                 $this->showTwitterXmlList($twitter_list);
1170             }
1171         }
1172
1173         $this->elementEnd('lists');
1174
1175         $this->element('next_cursor', null, $next_cursor);
1176         $this->element('previous_cursor', null, $prev_cursor);
1177
1178         $this->elementEnd('lists_list');
1179         $this->endDocument('xml');
1180     }
1181
1182     function showJsonLists($list, $next_cursor=0, $prev_cursor=0)
1183     {
1184         $this->initDocument('json');
1185
1186         $lists = array();
1187
1188         if (is_array($list)) {
1189             foreach ($list as $l) {
1190                 $twitter_list = $this->twitterListArray($l);
1191                 array_push($lists, $twitter_list);
1192             }
1193         } else {
1194             while ($list->fetch()) {
1195                 $twitter_list = $this->twitterListArray($list);
1196                 array_push($lists, $twitter_list);
1197             }
1198         }
1199
1200         $lists_list = array(
1201             'lists' => $lists,
1202             'next_cursor' => $next_cursor,
1203             'next_cursor_str' => strval($next_cursor),
1204             'previous_cursor' => $prev_cursor,
1205             'previous_cursor_str' => strval($prev_cursor)
1206         );
1207
1208         $this->showJsonObjects($lists_list);
1209
1210         $this->endDocument('json');
1211     }
1212
1213     function showTwitterXmlUsers($user)
1214     {
1215         $this->initDocument('xml');
1216         $this->elementStart('users', array('type' => 'array',
1217                                            'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
1218
1219         if (is_array($user)) {
1220             foreach ($user as $u) {
1221                 $twitter_user = $this->twitterUserArray($u);
1222                 $this->showTwitterXmlUser($twitter_user);
1223             }
1224         } else {
1225             while ($user->fetch()) {
1226                 $twitter_user = $this->twitterUserArray($user);
1227                 $this->showTwitterXmlUser($twitter_user);
1228             }
1229         }
1230
1231         $this->elementEnd('users');
1232         $this->endDocument('xml');
1233     }
1234
1235     function showJsonUsers($user)
1236     {
1237         $this->initDocument('json');
1238
1239         $users = array();
1240
1241         if (is_array($user)) {
1242             foreach ($user as $u) {
1243                 $twitter_user = $this->twitterUserArray($u);
1244                 array_push($users, $twitter_user);
1245             }
1246         } else {
1247             while ($user->fetch()) {
1248                 $twitter_user = $this->twitterUserArray($user);
1249                 array_push($users, $twitter_user);
1250             }
1251         }
1252
1253         $this->showJsonObjects($users);
1254
1255         $this->endDocument('json');
1256     }
1257
1258     function showSingleJsonGroup($group)
1259     {
1260         $this->initDocument('json');
1261         $twitter_group = $this->twitterGroupArray($group);
1262         $this->showJsonObjects($twitter_group);
1263         $this->endDocument('json');
1264     }
1265
1266     function showSingleXmlGroup($group)
1267     {
1268         $this->initDocument('xml');
1269         $twitter_group = $this->twitterGroupArray($group);
1270         $this->showTwitterXmlGroup($twitter_group);
1271         $this->endDocument('xml');
1272     }
1273
1274     function showSingleJsonList($list)
1275     {
1276         $this->initDocument('json');
1277         $twitter_list = $this->twitterListArray($list);
1278         $this->showJsonObjects($twitter_list);
1279         $this->endDocument('json');
1280     }
1281
1282     function showSingleXmlList($list)
1283     {
1284         $this->initDocument('xml');
1285         $twitter_list = $this->twitterListArray($list);
1286         $this->showTwitterXmlList($twitter_list);
1287         $this->endDocument('xml');
1288     }
1289
1290     function dateTwitter($dt)
1291     {
1292         $dateStr = date('d F Y H:i:s', strtotime($dt));
1293         $d = new DateTime($dateStr, new DateTimeZone('UTC'));
1294         $d->setTimezone(new DateTimeZone(common_timezone()));
1295         return $d->format('D M d H:i:s O Y');
1296     }
1297
1298     function initDocument($type='xml')
1299     {
1300         switch ($type) {
1301         case 'xml':
1302             header('Content-Type: application/xml; charset=utf-8');
1303             $this->startXML();
1304             break;
1305         case 'json':
1306             header('Content-Type: application/json; charset=utf-8');
1307
1308             // Check for JSONP callback
1309             if (isset($this->callback)) {
1310                 print $this->callback . '(';
1311             }
1312             break;
1313         case 'rss':
1314             header("Content-Type: application/rss+xml; charset=utf-8");
1315             $this->initTwitterRss();
1316             break;
1317         case 'atom':
1318             header('Content-Type: application/atom+xml; charset=utf-8');
1319             $this->initTwitterAtom();
1320             break;
1321         default:
1322             // TRANS: Client error on an API request with an unsupported data format.
1323             $this->clientError(_('Not a supported data format.'));
1324             break;
1325         }
1326
1327         return;
1328     }
1329
1330     function endDocument($type='xml')
1331     {
1332         switch ($type) {
1333         case 'xml':
1334             $this->endXML();
1335             break;
1336         case 'json':
1337             // Check for JSONP callback
1338             if (isset($this->callback)) {
1339                 print ')';
1340             }
1341             break;
1342         case 'rss':
1343             $this->endTwitterRss();
1344             break;
1345         case 'atom':
1346             $this->endTwitterRss();
1347             break;
1348         default:
1349             // TRANS: Client error on an API request with an unsupported data format.
1350             $this->clientError(_('Not a supported data format.'));
1351             break;
1352         }
1353         return;
1354     }
1355
1356     function clientError($msg, $code = 400, $format = null)
1357     {
1358         $action = $this->trimmed('action');
1359         if ($format === null) {
1360             $format = $this->format;
1361         }
1362
1363         common_debug("User error '$code' on '$action': $msg", __FILE__);
1364
1365         if (!array_key_exists($code, ClientErrorAction::$status)) {
1366             $code = 400;
1367         }
1368
1369         $status_string = ClientErrorAction::$status[$code];
1370
1371         // Do not emit error header for JSONP
1372         if (!isset($this->callback)) {
1373             header('HTTP/1.1 ' . $code . ' ' . $status_string);
1374         }
1375
1376         switch($format) {
1377         case 'xml':
1378             $this->initDocument('xml');
1379             $this->elementStart('hash');
1380             $this->element('error', null, $msg);
1381             $this->element('request', null, $_SERVER['REQUEST_URI']);
1382             $this->elementEnd('hash');
1383             $this->endDocument('xml');
1384             break;
1385         case 'json':
1386             $this->initDocument('json');
1387             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
1388             print(json_encode($error_array));
1389             $this->endDocument('json');
1390             break;
1391         case 'text':
1392             header('Content-Type: text/plain; charset=utf-8');
1393             print $msg;
1394             break;
1395         default:
1396             // If user didn't request a useful format, throw a regular client error
1397             throw new ClientException($msg, $code);
1398         }
1399     }
1400
1401     function serverError($msg, $code = 500, $content_type = null)
1402     {
1403         $action = $this->trimmed('action');
1404         if ($content_type === null) {
1405             $content_type = $this->format;
1406         }
1407
1408         common_debug("Server error '$code' on '$action': $msg", __FILE__);
1409
1410         if (!array_key_exists($code, ServerErrorAction::$status)) {
1411             $code = 400;
1412         }
1413
1414         $status_string = ServerErrorAction::$status[$code];
1415
1416         // Do not emit error header for JSONP
1417         if (!isset($this->callback)) {
1418             header('HTTP/1.1 '.$code.' '.$status_string);
1419         }
1420
1421         if ($content_type == 'xml') {
1422             $this->initDocument('xml');
1423             $this->elementStart('hash');
1424             $this->element('error', null, $msg);
1425             $this->element('request', null, $_SERVER['REQUEST_URI']);
1426             $this->elementEnd('hash');
1427             $this->endDocument('xml');
1428         } else {
1429             $this->initDocument('json');
1430             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
1431             print(json_encode($error_array));
1432             $this->endDocument('json');
1433         }
1434     }
1435
1436     function initTwitterRss()
1437     {
1438         $this->startXML();
1439         $this->elementStart(
1440             'rss',
1441             array(
1442                 'version'      => '2.0',
1443                 'xmlns:atom'   => 'http://www.w3.org/2005/Atom',
1444                 'xmlns:georss' => 'http://www.georss.org/georss'
1445             )
1446         );
1447         $this->elementStart('channel');
1448         Event::handle('StartApiRss', array($this));
1449     }
1450
1451     function endTwitterRss()
1452     {
1453         $this->elementEnd('channel');
1454         $this->elementEnd('rss');
1455         $this->endXML();
1456     }
1457
1458     function initTwitterAtom()
1459     {
1460         $this->startXML();
1461         // FIXME: don't hardcode the language here!
1462         $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
1463                                           'xml:lang' => 'en-US',
1464                                           'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
1465     }
1466
1467     function endTwitterAtom()
1468     {
1469         $this->elementEnd('feed');
1470         $this->endXML();
1471     }
1472
1473     function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
1474     {
1475         $profile_array = $this->twitterUserArray($profile, $includeStatuses);
1476         switch ($content_type) {
1477         case 'xml':
1478             $this->showTwitterXmlUser($profile_array);
1479             break;
1480         case 'json':
1481             $this->showJsonObjects($profile_array);
1482             break;
1483         default:
1484             // TRANS: Client error on an API request with an unsupported data format.
1485             $this->clientError(_('Not a supported data format.'));
1486             return;
1487         }
1488         return;
1489     }
1490
1491     private static function is_decimal($str)
1492     {
1493         return preg_match('/^[0-9]+$/', $str);
1494     }
1495
1496     function getTargetUser($id)
1497     {
1498         if (empty($id)) {
1499             // Twitter supports these other ways of passing the user ID
1500             if (self::is_decimal($this->arg('id'))) {
1501                 return User::staticGet($this->arg('id'));
1502             } else if ($this->arg('id')) {
1503                 $nickname = common_canonical_nickname($this->arg('id'));
1504                 return User::staticGet('nickname', $nickname);
1505             } else if ($this->arg('user_id')) {
1506                 // This is to ensure that a non-numeric user_id still
1507                 // overrides screen_name even if it doesn't get used
1508                 if (self::is_decimal($this->arg('user_id'))) {
1509                     return User::staticGet('id', $this->arg('user_id'));
1510                 }
1511             } else if ($this->arg('screen_name')) {
1512                 $nickname = common_canonical_nickname($this->arg('screen_name'));
1513                 return User::staticGet('nickname', $nickname);
1514             } else {
1515                 // Fall back to trying the currently authenticated user
1516                 return $this->auth_user;
1517             }
1518
1519         } else if (self::is_decimal($id)) {
1520             return User::staticGet($id);
1521         } else {
1522             $nickname = common_canonical_nickname($id);
1523             return User::staticGet('nickname', $nickname);
1524         }
1525     }
1526
1527     function getTargetProfile($id)
1528     {
1529         if (empty($id)) {
1530
1531             // Twitter supports these other ways of passing the user ID
1532             if (self::is_decimal($this->arg('id'))) {
1533                 return Profile::staticGet($this->arg('id'));
1534             } else if ($this->arg('id')) {
1535                 // Screen names currently can only uniquely identify a local user.
1536                 $nickname = common_canonical_nickname($this->arg('id'));
1537                 $user = User::staticGet('nickname', $nickname);
1538                 return $user ? $user->getProfile() : null;
1539             } else if ($this->arg('user_id')) {
1540                 // This is to ensure that a non-numeric user_id still
1541                 // overrides screen_name even if it doesn't get used
1542                 if (self::is_decimal($this->arg('user_id'))) {
1543                     return Profile::staticGet('id', $this->arg('user_id'));
1544                 }
1545             } else if ($this->arg('screen_name')) {
1546                 $nickname = common_canonical_nickname($this->arg('screen_name'));
1547                 $user = User::staticGet('nickname', $nickname);
1548                 return $user ? $user->getProfile() : null;
1549             }
1550         } else if (self::is_decimal($id)) {
1551             return Profile::staticGet($id);
1552         } else {
1553             $nickname = common_canonical_nickname($id);
1554             $user = User::staticGet('nickname', $nickname);
1555             return $user ? $user->getProfile() : null;
1556         }
1557     }
1558
1559     function getTargetGroup($id)
1560     {
1561         if (empty($id)) {
1562             if (self::is_decimal($this->arg('id'))) {
1563                 return User_group::staticGet('id', $this->arg('id'));
1564             } else if ($this->arg('id')) {
1565                 return User_group::getForNickname($this->arg('id'));
1566             } else if ($this->arg('group_id')) {
1567                 // This is to ensure that a non-numeric group_id still
1568                 // overrides group_name even if it doesn't get used
1569                 if (self::is_decimal($this->arg('group_id'))) {
1570                     return User_group::staticGet('id', $this->arg('group_id'));
1571                 }
1572             } else if ($this->arg('group_name')) {
1573                 return User_group::getForNickname($this->arg('group_name'));
1574             }
1575
1576         } else if (self::is_decimal($id)) {
1577             return User_group::staticGet('id', $id);
1578         } else {
1579             return User_group::getForNickname($id);
1580         }
1581     }
1582
1583     function getTargetList($user=null, $id=null)
1584     {
1585         $tagger = $this->getTargetUser($user);
1586         $list = null;
1587
1588         if (empty($id)) {
1589             $id = $this->arg('id');
1590         }
1591
1592         if($id) {
1593             if (is_numeric($id)) {
1594                 $list = Profile_list::staticGet('id', $id);
1595
1596                 // only if the list with the id belongs to the tagger
1597                 if(empty($list) || $list->tagger != $tagger->id) {
1598                     $list = null;
1599                 }
1600             }
1601             if (empty($list)) {
1602                 $tag = common_canonical_tag($id);
1603                 $list = Profile_list::getByTaggerAndTag($tagger->id, $tag);
1604             }
1605
1606             if (!empty($list) && $list->private) {
1607                 if ($this->auth_user->id == $list->tagger) {
1608                     return $list;
1609                 }
1610             } else {
1611                 return $list;
1612             }
1613         }
1614         return null;
1615     }
1616
1617     /**
1618      * Returns query argument or default value if not found. Certain
1619      * parameters used throughout the API are lightly scrubbed and
1620      * bounds checked.  This overrides Action::arg().
1621      *
1622      * @param string $key requested argument
1623      * @param string $def default value to return if $key is not provided
1624      *
1625      * @return var $var
1626      */
1627     function arg($key, $def=null)
1628     {
1629         // XXX: Do even more input validation/scrubbing?
1630
1631         if (array_key_exists($key, $this->args)) {
1632             switch($key) {
1633             case 'page':
1634                 $page = (int)$this->args['page'];
1635                 return ($page < 1) ? 1 : $page;
1636             case 'count':
1637                 $count = (int)$this->args['count'];
1638                 if ($count < 1) {
1639                     return 20;
1640                 } elseif ($count > 200) {
1641                     return 200;
1642                 } else {
1643                     return $count;
1644                 }
1645             case 'since_id':
1646                 $since_id = (int)$this->args['since_id'];
1647                 return ($since_id < 1) ? 0 : $since_id;
1648             case 'max_id':
1649                 $max_id = (int)$this->args['max_id'];
1650                 return ($max_id < 1) ? 0 : $max_id;
1651             default:
1652                 return parent::arg($key, $def);
1653             }
1654         } else {
1655             return $def;
1656         }
1657     }
1658
1659     /**
1660      * Calculate the complete URI that called up this action.  Used for
1661      * Atom rel="self" links.  Warning: this is funky.
1662      *
1663      * @return string URL    a URL suitable for rel="self" Atom links
1664      */
1665     function getSelfUri()
1666     {
1667         $action = mb_substr(get_class($this), 0, -6); // remove 'Action'
1668
1669         $id = $this->arg('id');
1670         $aargs = array('format' => $this->format);
1671         if (!empty($id)) {
1672             $aargs['id'] = $id;
1673         }
1674
1675         $tag = $this->arg('tag');
1676         if (!empty($tag)) {
1677             $aargs['tag'] = $tag;
1678         }
1679
1680         parse_str($_SERVER['QUERY_STRING'], $params);
1681         $pstring = '';
1682         if (!empty($params)) {
1683             unset($params['p']);
1684             $pstring = http_build_query($params);
1685         }
1686
1687         $uri = common_local_url($action, $aargs);
1688
1689         if (!empty($pstring)) {
1690             $uri .= '?' . $pstring;
1691         }
1692
1693         return $uri;
1694     }
1695 }