apiaction.php 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
  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. /* External API usage documentation. Please update when you change how the API works. */
  36. /*! @mainpage StatusNet REST API
  37. @section Introduction
  38. Some explanatory text about the API would be nice.
  39. @section API Methods
  40. @subsection timelinesmethods_sec Timeline Methods
  41. @li @ref publictimeline
  42. @li @ref friendstimeline
  43. @subsection statusmethods_sec Status Methods
  44. @li @ref statusesupdate
  45. @subsection usermethods_sec User Methods
  46. @subsection directmessagemethods_sec Direct Message Methods (now a plugin)
  47. @subsection friendshipmethods_sec Friendship Methods
  48. @subsection socialgraphmethods_sec Social Graph Methods
  49. @subsection accountmethods_sec Account Methods
  50. @subsection favoritesmethods_sec Favorites Methods
  51. @subsection blockmethods_sec Block Methods
  52. @subsection oauthmethods_sec OAuth Methods
  53. @subsection helpmethods_sec Help Methods
  54. @subsection groupmethods_sec Group Methods
  55. @page apiroot API Root
  56. The URLs for methods referred to in this API documentation are
  57. relative to the StatusNet API root. The API root is determined by the
  58. site's @b server and @b path variables, which are generally specified
  59. in config.php. For example:
  60. @code
  61. $config['site']['server'] = 'example.org';
  62. $config['site']['path'] = 'statusnet'
  63. @endcode
  64. The pattern for a site's API root is: @c protocol://server/path/api E.g:
  65. @c http://example.org/statusnet/api
  66. The @b path can be empty. In that case the API root would simply be:
  67. @c http://example.org/api
  68. */
  69. if (!defined('STATUSNET')) {
  70. exit(1);
  71. }
  72. class ApiValidationException extends Exception { }
  73. /**
  74. * Contains most of the Twitter-compatible API output functions.
  75. *
  76. * @category API
  77. * @package StatusNet
  78. * @author Craig Andrews <candrews@integralblue.com>
  79. * @author Dan Moore <dan@moore.cx>
  80. * @author Evan Prodromou <evan@status.net>
  81. * @author Jeffery To <jeffery.to@gmail.com>
  82. * @author Toby Inkster <mail@tobyinkster.co.uk>
  83. * @author Zach Copley <zach@status.net>
  84. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  85. * @link http://status.net/
  86. */
  87. class ApiAction extends Action
  88. {
  89. const READ_ONLY = 1;
  90. const READ_WRITE = 2;
  91. var $user = null;
  92. var $auth_user = null;
  93. var $page = null;
  94. var $count = null;
  95. var $offset = null;
  96. var $limit = null;
  97. var $max_id = null;
  98. var $since_id = null;
  99. var $source = null;
  100. var $callback = null;
  101. var $format = null;
  102. var $access = self::READ_ONLY; // read (default) or read-write
  103. static $reserved_sources = array('web', 'omb', 'ostatus', 'mail', 'xmpp', 'api');
  104. /**
  105. * Initialization.
  106. *
  107. * @param array $args Web and URL arguments
  108. *
  109. * @return boolean false if user doesn't exist
  110. */
  111. protected function prepare(array $args=array())
  112. {
  113. StatusNet::setApi(true); // reduce exception reports to aid in debugging
  114. parent::prepare($args);
  115. $this->format = $this->arg('format');
  116. $this->callback = $this->arg('callback');
  117. $this->page = (int)$this->arg('page', 1);
  118. $this->count = (int)$this->arg('count', 20);
  119. $this->max_id = (int)$this->arg('max_id', 0);
  120. $this->since_id = (int)$this->arg('since_id', 0);
  121. // These two are not used everywhere, mainly just AtompubAction extensions
  122. $this->offset = ($this->page-1) * $this->count;
  123. $this->limit = $this->count + 1;
  124. if ($this->arg('since')) {
  125. header('X-StatusNet-Warning: since parameter is disabled; use since_id');
  126. }
  127. $this->source = $this->trimmed('source');
  128. if (empty($this->source) || in_array($this->source, self::$reserved_sources)) {
  129. $this->source = 'api';
  130. }
  131. return true;
  132. }
  133. /**
  134. * Handle a request
  135. *
  136. * @param array $args Arguments from $_REQUEST
  137. *
  138. * @return void
  139. */
  140. protected function handle()
  141. {
  142. header('Access-Control-Allow-Origin: *');
  143. parent::handle();
  144. }
  145. /**
  146. * Overrides XMLOutputter::element to write booleans as strings (true|false).
  147. * See that method's documentation for more info.
  148. *
  149. * @param string $tag Element type or tagname
  150. * @param array $attrs Array of element attributes, as
  151. * key-value pairs
  152. * @param string $content string content of the element
  153. *
  154. * @return void
  155. */
  156. function element($tag, $attrs=null, $content=null)
  157. {
  158. if (is_bool($content)) {
  159. $content = ($content ? 'true' : 'false');
  160. }
  161. return parent::element($tag, $attrs, $content);
  162. }
  163. function twitterUserArray($profile, $get_notice=false)
  164. {
  165. $twitter_user = array();
  166. try {
  167. $user = $profile->getUser();
  168. } catch (NoSuchUserException $e) {
  169. $user = null;
  170. }
  171. $twitter_user['id'] = intval($profile->id);
  172. $twitter_user['name'] = $profile->getBestName();
  173. $twitter_user['screen_name'] = $profile->nickname;
  174. $twitter_user['location'] = ($profile->location) ? $profile->location : null;
  175. $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
  176. // TODO: avatar url template (example.com/user/avatar?size={x}x{y})
  177. $twitter_user['profile_image_url'] = Avatar::urlByProfile($profile, AVATAR_STREAM_SIZE);
  178. $twitter_user['profile_image_url_https'] = $twitter_user['profile_image_url'];
  179. // START introduced by qvitter API, not necessary for StatusNet API
  180. $twitter_user['profile_image_url_profile_size'] = Avatar::urlByProfile($profile, AVATAR_PROFILE_SIZE);
  181. try {
  182. $avatar = Avatar::getUploaded($profile);
  183. $origurl = $avatar->displayUrl();
  184. } catch (Exception $e) {
  185. $origurl = $twitter_user['profile_image_url_profile_size'];
  186. }
  187. $twitter_user['profile_image_url_original'] = $origurl;
  188. $twitter_user['groups_count'] = $profile->getGroupCount();
  189. foreach (array('linkcolor', 'backgroundcolor') as $key) {
  190. $twitter_user[$key] = Profile_prefs::getConfigData($profile, 'theme', $key);
  191. }
  192. // END introduced by qvitter API, not necessary for StatusNet API
  193. $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
  194. $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false;
  195. $twitter_user['followers_count'] = $profile->subscriberCount();
  196. // Note: some profiles don't have an associated user
  197. $twitter_user['friends_count'] = $profile->subscriptionCount();
  198. $twitter_user['created_at'] = $this->dateTwitter($profile->created);
  199. $timezone = 'UTC';
  200. if (!empty($user) && $user->timezone) {
  201. $timezone = $user->timezone;
  202. }
  203. $t = new DateTime;
  204. $t->setTimezone(new DateTimeZone($timezone));
  205. $twitter_user['utc_offset'] = $t->format('Z');
  206. $twitter_user['time_zone'] = $timezone;
  207. $twitter_user['statuses_count'] = $profile->noticeCount();
  208. // Is the requesting user following this user?
  209. $twitter_user['following'] = false;
  210. $twitter_user['statusnet_blocking'] = false;
  211. $twitter_user['notifications'] = false;
  212. if (isset($this->auth_user)) {
  213. $twitter_user['following'] = $this->auth_user->isSubscribed($profile);
  214. $twitter_user['statusnet_blocking'] = $this->auth_user->hasBlocked($profile);
  215. // Notifications on?
  216. $sub = Subscription::pkeyGet(array('subscriber' =>
  217. $this->auth_user->id,
  218. 'subscribed' => $profile->id));
  219. if ($sub) {
  220. $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
  221. }
  222. }
  223. if ($get_notice) {
  224. $notice = $profile->getCurrentNotice();
  225. if ($notice instanceof Notice) {
  226. // don't get user!
  227. $twitter_user['status'] = $this->twitterStatusArray($notice, false);
  228. }
  229. }
  230. // StatusNet-specific
  231. $twitter_user['statusnet_profile_url'] = $profile->profileurl;
  232. // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array
  233. Event::handle('TwitterUserArray', array($profile, &$twitter_user, $this->scoped, array()));
  234. return $twitter_user;
  235. }
  236. function twitterStatusArray($notice, $include_user=true)
  237. {
  238. $base = $this->twitterSimpleStatusArray($notice, $include_user);
  239. if (!empty($notice->repeat_of)) {
  240. $original = Notice::getKV('id', $notice->repeat_of);
  241. if ($original instanceof Notice) {
  242. $orig_array = $this->twitterSimpleStatusArray($original, $include_user);
  243. $base['retweeted_status'] = $orig_array;
  244. }
  245. }
  246. return $base;
  247. }
  248. function twitterSimpleStatusArray($notice, $include_user=true)
  249. {
  250. $profile = $notice->getProfile();
  251. $twitter_status = array();
  252. $twitter_status['text'] = $notice->content;
  253. $twitter_status['truncated'] = false; # Not possible on StatusNet
  254. $twitter_status['created_at'] = $this->dateTwitter($notice->created);
  255. try {
  256. // We could just do $notice->reply_to but maybe the future holds a
  257. // different story for parenting.
  258. $parent = $notice->getParent();
  259. $in_reply_to = $parent->id;
  260. } catch (Exception $e) {
  261. $in_reply_to = null;
  262. }
  263. $twitter_status['in_reply_to_status_id'] = $in_reply_to;
  264. $source = null;
  265. $ns = $notice->getSource();
  266. if ($ns instanceof Notice_source) {
  267. if (!empty($ns->name) && !empty($ns->url)) {
  268. $source = '<a href="'
  269. . htmlspecialchars($ns->url)
  270. . '" rel="nofollow">'
  271. . htmlspecialchars($ns->name)
  272. . '</a>';
  273. } else {
  274. $source = $ns->code;
  275. }
  276. }
  277. $twitter_status['uri'] = $notice->getUri();
  278. $twitter_status['source'] = $source;
  279. $twitter_status['id'] = intval($notice->id);
  280. $replier_profile = null;
  281. if ($notice->reply_to) {
  282. $reply = Notice::getKV(intval($notice->reply_to));
  283. if ($reply) {
  284. $replier_profile = $reply->getProfile();
  285. }
  286. }
  287. $twitter_status['in_reply_to_user_id'] =
  288. ($replier_profile) ? intval($replier_profile->id) : null;
  289. $twitter_status['in_reply_to_screen_name'] =
  290. ($replier_profile) ? $replier_profile->nickname : null;
  291. if (isset($notice->lat) && isset($notice->lon)) {
  292. // This is the format that GeoJSON expects stuff to be in
  293. $twitter_status['geo'] = array('type' => 'Point',
  294. 'coordinates' => array((float) $notice->lat,
  295. (float) $notice->lon));
  296. } else {
  297. $twitter_status['geo'] = null;
  298. }
  299. if (!is_null($this->scoped)) {
  300. $twitter_status['repeated'] = $this->scoped->hasRepeated($notice);
  301. } else {
  302. $twitter_status['repeated'] = false;
  303. }
  304. // Enclosures
  305. $attachments = $notice->attachments();
  306. if (!empty($attachments)) {
  307. $twitter_status['attachments'] = array();
  308. foreach ($attachments as $attachment) {
  309. try {
  310. $enclosure_o = $attachment->getEnclosure();
  311. $enclosure = array();
  312. $enclosure['url'] = $enclosure_o->url;
  313. $enclosure['mimetype'] = $enclosure_o->mimetype;
  314. $enclosure['size'] = $enclosure_o->size;
  315. $twitter_status['attachments'][] = $enclosure;
  316. } catch (ServerException $e) {
  317. // There was not enough metadata available
  318. }
  319. }
  320. }
  321. if ($include_user && $profile) {
  322. // Don't get notice (recursive!)
  323. $twitter_user = $this->twitterUserArray($profile, false);
  324. $twitter_status['user'] = $twitter_user;
  325. }
  326. // StatusNet-specific
  327. $twitter_status['statusnet_html'] = $notice->rendered;
  328. $twitter_status['statusnet_conversation_id'] = intval($notice->conversation);
  329. // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array
  330. Event::handle('NoticeSimpleStatusArray', array($notice, &$twitter_status, $this->scoped,
  331. array('include_user'=>$include_user)));
  332. return $twitter_status;
  333. }
  334. function twitterGroupArray($group)
  335. {
  336. $twitter_group = array();
  337. $twitter_group['id'] = intval($group->id);
  338. $twitter_group['url'] = $group->permalink();
  339. $twitter_group['nickname'] = $group->nickname;
  340. $twitter_group['fullname'] = $group->fullname;
  341. if (isset($this->auth_user)) {
  342. $twitter_group['member'] = $this->auth_user->isMember($group);
  343. $twitter_group['blocked'] = Group_block::isBlocked(
  344. $group,
  345. $this->auth_user->getProfile()
  346. );
  347. }
  348. $twitter_group['admin_count'] = $group->getAdminCount();
  349. $twitter_group['member_count'] = $group->getMemberCount();
  350. $twitter_group['original_logo'] = $group->original_logo;
  351. $twitter_group['homepage_logo'] = $group->homepage_logo;
  352. $twitter_group['stream_logo'] = $group->stream_logo;
  353. $twitter_group['mini_logo'] = $group->mini_logo;
  354. $twitter_group['homepage'] = $group->homepage;
  355. $twitter_group['description'] = $group->description;
  356. $twitter_group['location'] = $group->location;
  357. $twitter_group['created'] = $this->dateTwitter($group->created);
  358. $twitter_group['modified'] = $this->dateTwitter($group->modified);
  359. return $twitter_group;
  360. }
  361. function twitterRssGroupArray($group)
  362. {
  363. $entry = array();
  364. $entry['content']=$group->description;
  365. $entry['title']=$group->nickname;
  366. $entry['link']=$group->permalink();
  367. $entry['published']=common_date_iso8601($group->created);
  368. $entry['updated']==common_date_iso8601($group->modified);
  369. $taguribase = common_config('integration', 'groupuri');
  370. $entry['id'] = "group:$groupuribase:$entry[link]";
  371. $entry['description'] = $entry['content'];
  372. $entry['pubDate'] = common_date_rfc2822($group->created);
  373. $entry['guid'] = $entry['link'];
  374. return $entry;
  375. }
  376. function twitterListArray($list)
  377. {
  378. $profile = Profile::getKV('id', $list->tagger);
  379. $twitter_list = array();
  380. $twitter_list['id'] = $list->id;
  381. $twitter_list['name'] = $list->tag;
  382. $twitter_list['full_name'] = '@'.$profile->nickname.'/'.$list->tag;;
  383. $twitter_list['slug'] = $list->tag;
  384. $twitter_list['description'] = $list->description;
  385. $twitter_list['subscriber_count'] = $list->subscriberCount();
  386. $twitter_list['member_count'] = $list->taggedCount();
  387. $twitter_list['uri'] = $list->getUri();
  388. if (isset($this->auth_user)) {
  389. $twitter_list['following'] = $list->hasSubscriber($this->auth_user);
  390. } else {
  391. $twitter_list['following'] = false;
  392. }
  393. $twitter_list['mode'] = ($list->private) ? 'private' : 'public';
  394. $twitter_list['user'] = $this->twitterUserArray($profile, false);
  395. return $twitter_list;
  396. }
  397. function twitterRssEntryArray($notice)
  398. {
  399. $entry = array();
  400. if (Event::handle('StartRssEntryArray', array($notice, &$entry))) {
  401. $profile = $notice->getProfile();
  402. // We trim() to avoid extraneous whitespace in the output
  403. $entry['content'] = common_xml_safe_str(trim($notice->rendered));
  404. $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
  405. $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
  406. $entry['published'] = common_date_iso8601($notice->created);
  407. $taguribase = TagURI::base();
  408. $entry['id'] = "tag:$taguribase:$entry[link]";
  409. $entry['updated'] = $entry['published'];
  410. $entry['author'] = $profile->getBestName();
  411. // Enclosures
  412. $attachments = $notice->attachments();
  413. $enclosures = array();
  414. foreach ($attachments as $attachment) {
  415. try {
  416. $enclosure_o = $attachment->getEnclosure();
  417. $enclosure = array();
  418. $enclosure['url'] = $enclosure_o->url;
  419. $enclosure['mimetype'] = $enclosure_o->mimetype;
  420. $enclosure['size'] = $enclosure_o->size;
  421. $enclosures[] = $enclosure;
  422. } catch (ServerException $e) {
  423. // There was not enough metadata available
  424. }
  425. }
  426. if (!empty($enclosures)) {
  427. $entry['enclosures'] = $enclosures;
  428. }
  429. // Tags/Categories
  430. $tag = new Notice_tag();
  431. $tag->notice_id = $notice->id;
  432. if ($tag->find()) {
  433. $entry['tags']=array();
  434. while ($tag->fetch()) {
  435. $entry['tags'][]=$tag->tag;
  436. }
  437. }
  438. $tag->free();
  439. // RSS Item specific
  440. $entry['description'] = $entry['content'];
  441. $entry['pubDate'] = common_date_rfc2822($notice->created);
  442. $entry['guid'] = $entry['link'];
  443. if (isset($notice->lat) && isset($notice->lon)) {
  444. // This is the format that GeoJSON expects stuff to be in.
  445. // showGeoRSS() below uses it for XML output, so we reuse it
  446. $entry['geo'] = array('type' => 'Point',
  447. 'coordinates' => array((float) $notice->lat,
  448. (float) $notice->lon));
  449. } else {
  450. $entry['geo'] = null;
  451. }
  452. Event::handle('EndRssEntryArray', array($notice, &$entry));
  453. }
  454. return $entry;
  455. }
  456. function twitterRelationshipArray($source, $target)
  457. {
  458. $relationship = array();
  459. $relationship['source'] =
  460. $this->relationshipDetailsArray($source, $target);
  461. $relationship['target'] =
  462. $this->relationshipDetailsArray($target, $source);
  463. return array('relationship' => $relationship);
  464. }
  465. function relationshipDetailsArray($source, $target)
  466. {
  467. $details = array();
  468. $source_profile = $source->getProfile();
  469. $target_profile = $target->getProfile();
  470. $details['screen_name'] = $source->nickname;
  471. $details['followed_by'] = $target->isSubscribed($source_profile);
  472. $details['following'] = $source->isSubscribed($target_profile);
  473. $notifications = false;
  474. if ($source->isSubscribed($target_profile)) {
  475. $sub = Subscription::pkeyGet(array('subscriber' =>
  476. $source->id, 'subscribed' => $target->id));
  477. if (!empty($sub)) {
  478. $notifications = ($sub->jabber || $sub->sms);
  479. }
  480. }
  481. $details['notifications_enabled'] = $notifications;
  482. $details['blocking'] = $source->hasBlocked($target_profile);
  483. $details['id'] = intval($source->id);
  484. return $details;
  485. }
  486. function showTwitterXmlRelationship($relationship)
  487. {
  488. $this->elementStart('relationship');
  489. foreach($relationship as $element => $value) {
  490. if ($element == 'source' || $element == 'target') {
  491. $this->elementStart($element);
  492. $this->showXmlRelationshipDetails($value);
  493. $this->elementEnd($element);
  494. }
  495. }
  496. $this->elementEnd('relationship');
  497. }
  498. function showXmlRelationshipDetails($details)
  499. {
  500. foreach($details as $element => $value) {
  501. $this->element($element, null, $value);
  502. }
  503. }
  504. function showTwitterXmlStatus($twitter_status, $tag='status', $namespaces=false)
  505. {
  506. $attrs = array();
  507. if ($namespaces) {
  508. $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
  509. }
  510. $this->elementStart($tag, $attrs);
  511. foreach($twitter_status as $element => $value) {
  512. switch ($element) {
  513. case 'user':
  514. $this->showTwitterXmlUser($twitter_status['user']);
  515. break;
  516. case 'text':
  517. $this->element($element, null, common_xml_safe_str($value));
  518. break;
  519. case 'attachments':
  520. $this->showXmlAttachments($twitter_status['attachments']);
  521. break;
  522. case 'geo':
  523. $this->showGeoXML($value);
  524. break;
  525. case 'retweeted_status':
  526. $this->showTwitterXmlStatus($value, 'retweeted_status');
  527. break;
  528. default:
  529. if (strncmp($element, 'statusnet_', 10) == 0) {
  530. $this->element('statusnet:'.substr($element, 10), null, $value);
  531. } else {
  532. $this->element($element, null, $value);
  533. }
  534. }
  535. }
  536. $this->elementEnd($tag);
  537. }
  538. function showTwitterXmlGroup($twitter_group)
  539. {
  540. $this->elementStart('group');
  541. foreach($twitter_group as $element => $value) {
  542. $this->element($element, null, $value);
  543. }
  544. $this->elementEnd('group');
  545. }
  546. function showTwitterXmlList($twitter_list)
  547. {
  548. $this->elementStart('list');
  549. foreach($twitter_list as $element => $value) {
  550. if($element == 'user') {
  551. $this->showTwitterXmlUser($value, 'user');
  552. }
  553. else {
  554. $this->element($element, null, $value);
  555. }
  556. }
  557. $this->elementEnd('list');
  558. }
  559. function showTwitterXmlUser($twitter_user, $role='user', $namespaces=false)
  560. {
  561. $attrs = array();
  562. if ($namespaces) {
  563. $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
  564. }
  565. $this->elementStart($role, $attrs);
  566. foreach($twitter_user as $element => $value) {
  567. if ($element == 'status') {
  568. $this->showTwitterXmlStatus($twitter_user['status']);
  569. } else if (strncmp($element, 'statusnet_', 10) == 0) {
  570. $this->element('statusnet:'.substr($element, 10), null, $value);
  571. } else {
  572. $this->element($element, null, $value);
  573. }
  574. }
  575. $this->elementEnd($role);
  576. }
  577. function showXmlAttachments($attachments) {
  578. if (!empty($attachments)) {
  579. $this->elementStart('attachments', array('type' => 'array'));
  580. foreach ($attachments as $attachment) {
  581. $attrs = array();
  582. $attrs['url'] = $attachment['url'];
  583. $attrs['mimetype'] = $attachment['mimetype'];
  584. $attrs['size'] = $attachment['size'];
  585. $this->element('enclosure', $attrs, '');
  586. }
  587. $this->elementEnd('attachments');
  588. }
  589. }
  590. function showGeoXML($geo)
  591. {
  592. if (empty($geo)) {
  593. // empty geo element
  594. $this->element('geo');
  595. } else {
  596. $this->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss'));
  597. $this->element('georss:point', null, $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]);
  598. $this->elementEnd('geo');
  599. }
  600. }
  601. function showGeoRSS($geo)
  602. {
  603. if (!empty($geo)) {
  604. $this->element(
  605. 'georss:point',
  606. null,
  607. $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]
  608. );
  609. }
  610. }
  611. function showTwitterRssItem($entry)
  612. {
  613. $this->elementStart('item');
  614. $this->element('title', null, $entry['title']);
  615. $this->element('description', null, $entry['description']);
  616. $this->element('pubDate', null, $entry['pubDate']);
  617. $this->element('guid', null, $entry['guid']);
  618. $this->element('link', null, $entry['link']);
  619. // RSS only supports 1 enclosure per item
  620. if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
  621. $enclosure = $entry['enclosures'][0];
  622. $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
  623. }
  624. if(array_key_exists('tags', $entry)){
  625. foreach($entry['tags'] as $tag){
  626. $this->element('category', null,$tag);
  627. }
  628. }
  629. $this->showGeoRSS($entry['geo']);
  630. $this->elementEnd('item');
  631. }
  632. function showJsonObjects($objects)
  633. {
  634. print(json_encode($objects));
  635. }
  636. function showSingleXmlStatus($notice)
  637. {
  638. $this->initDocument('xml');
  639. $twitter_status = $this->twitterStatusArray($notice);
  640. $this->showTwitterXmlStatus($twitter_status, 'status', true);
  641. $this->endDocument('xml');
  642. }
  643. function showSingleAtomStatus($notice)
  644. {
  645. header('Content-Type: application/atom+xml; charset=utf-8');
  646. print $notice->asAtomEntry(true, true, true, $this->auth_user->getProfile());
  647. }
  648. function show_single_json_status($notice)
  649. {
  650. $this->initDocument('json');
  651. $status = $this->twitterStatusArray($notice);
  652. $this->showJsonObjects($status);
  653. $this->endDocument('json');
  654. }
  655. function showXmlTimeline($notice)
  656. {
  657. $this->initDocument('xml');
  658. $this->elementStart('statuses', array('type' => 'array',
  659. 'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
  660. if (is_array($notice)) {
  661. $notice = new ArrayWrapper($notice);
  662. }
  663. while ($notice->fetch()) {
  664. try {
  665. $twitter_status = $this->twitterStatusArray($notice);
  666. $this->showTwitterXmlStatus($twitter_status);
  667. } catch (Exception $e) {
  668. common_log(LOG_ERR, $e->getMessage());
  669. continue;
  670. }
  671. }
  672. $this->elementEnd('statuses');
  673. $this->endDocument('xml');
  674. }
  675. function showRssTimeline($notice, $title, $link, $subtitle, $suplink = null, $logo = null, $self = null)
  676. {
  677. $this->initDocument('rss');
  678. $this->element('title', null, $title);
  679. $this->element('link', null, $link);
  680. if (!is_null($self)) {
  681. $this->element(
  682. 'atom:link',
  683. array(
  684. 'type' => 'application/rss+xml',
  685. 'href' => $self,
  686. 'rel' => 'self'
  687. )
  688. );
  689. }
  690. if (!is_null($suplink)) {
  691. // For FriendFeed's SUP protocol
  692. $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
  693. 'rel' => 'http://api.friendfeed.com/2008/03#sup',
  694. 'href' => $suplink,
  695. 'type' => 'application/json'));
  696. }
  697. if (!is_null($logo)) {
  698. $this->elementStart('image');
  699. $this->element('link', null, $link);
  700. $this->element('title', null, $title);
  701. $this->element('url', null, $logo);
  702. $this->elementEnd('image');
  703. }
  704. $this->element('description', null, $subtitle);
  705. $this->element('language', null, 'en-us');
  706. $this->element('ttl', null, '40');
  707. if (is_array($notice)) {
  708. $notice = new ArrayWrapper($notice);
  709. }
  710. while ($notice->fetch()) {
  711. try {
  712. $entry = $this->twitterRssEntryArray($notice);
  713. $this->showTwitterRssItem($entry);
  714. } catch (Exception $e) {
  715. common_log(LOG_ERR, $e->getMessage());
  716. // continue on exceptions
  717. }
  718. }
  719. $this->endTwitterRss();
  720. }
  721. function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null)
  722. {
  723. $this->initDocument('atom');
  724. $this->element('title', null, $title);
  725. $this->element('id', null, $id);
  726. $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
  727. if (!is_null($logo)) {
  728. $this->element('logo',null,$logo);
  729. }
  730. if (!is_null($suplink)) {
  731. // For FriendFeed's SUP protocol
  732. $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
  733. 'href' => $suplink,
  734. 'type' => 'application/json'));
  735. }
  736. if (!is_null($selfuri)) {
  737. $this->element('link', array('href' => $selfuri,
  738. 'rel' => 'self', 'type' => 'application/atom+xml'), null);
  739. }
  740. $this->element('updated', null, common_date_iso8601('now'));
  741. $this->element('subtitle', null, $subtitle);
  742. if (is_array($notice)) {
  743. $notice = new ArrayWrapper($notice);
  744. }
  745. while ($notice->fetch()) {
  746. try {
  747. $this->raw($notice->asAtomEntry());
  748. } catch (Exception $e) {
  749. common_log(LOG_ERR, $e->getMessage());
  750. continue;
  751. }
  752. }
  753. $this->endDocument('atom');
  754. }
  755. function showRssGroups($group, $title, $link, $subtitle)
  756. {
  757. $this->initDocument('rss');
  758. $this->element('title', null, $title);
  759. $this->element('link', null, $link);
  760. $this->element('description', null, $subtitle);
  761. $this->element('language', null, 'en-us');
  762. $this->element('ttl', null, '40');
  763. if (is_array($group)) {
  764. foreach ($group as $g) {
  765. $twitter_group = $this->twitterRssGroupArray($g);
  766. $this->showTwitterRssItem($twitter_group);
  767. }
  768. } else {
  769. while ($group->fetch()) {
  770. $twitter_group = $this->twitterRssGroupArray($group);
  771. $this->showTwitterRssItem($twitter_group);
  772. }
  773. }
  774. $this->endTwitterRss();
  775. }
  776. function showTwitterAtomEntry($entry)
  777. {
  778. $this->elementStart('entry');
  779. $this->element('title', null, common_xml_safe_str($entry['title']));
  780. $this->element(
  781. 'content',
  782. array('type' => 'html'),
  783. common_xml_safe_str($entry['content'])
  784. );
  785. $this->element('id', null, $entry['id']);
  786. $this->element('published', null, $entry['published']);
  787. $this->element('updated', null, $entry['updated']);
  788. $this->element('link', array('type' => 'text/html',
  789. 'href' => $entry['link'],
  790. 'rel' => 'alternate'));
  791. $this->element('link', array('type' => $entry['avatar-type'],
  792. 'href' => $entry['avatar'],
  793. 'rel' => 'image'));
  794. $this->elementStart('author');
  795. $this->element('name', null, $entry['author-name']);
  796. $this->element('uri', null, $entry['author-uri']);
  797. $this->elementEnd('author');
  798. $this->elementEnd('entry');
  799. }
  800. function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
  801. {
  802. $this->initDocument('atom');
  803. $this->element('title', null, common_xml_safe_str($title));
  804. $this->element('id', null, $id);
  805. $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
  806. if (!is_null($selfuri)) {
  807. $this->element('link', array('href' => $selfuri,
  808. 'rel' => 'self', 'type' => 'application/atom+xml'), null);
  809. }
  810. $this->element('updated', null, common_date_iso8601('now'));
  811. $this->element('subtitle', null, common_xml_safe_str($subtitle));
  812. if (is_array($group)) {
  813. foreach ($group as $g) {
  814. $this->raw($g->asAtomEntry());
  815. }
  816. } else {
  817. while ($group->fetch()) {
  818. $this->raw($group->asAtomEntry());
  819. }
  820. }
  821. $this->endDocument('atom');
  822. }
  823. function showJsonTimeline($notice)
  824. {
  825. $this->initDocument('json');
  826. $statuses = array();
  827. if (is_array($notice)) {
  828. $notice = new ArrayWrapper($notice);
  829. }
  830. while ($notice->fetch()) {
  831. try {
  832. $twitter_status = $this->twitterStatusArray($notice);
  833. array_push($statuses, $twitter_status);
  834. } catch (Exception $e) {
  835. common_log(LOG_ERR, $e->getMessage());
  836. continue;
  837. }
  838. }
  839. $this->showJsonObjects($statuses);
  840. $this->endDocument('json');
  841. }
  842. function showJsonGroups($group)
  843. {
  844. $this->initDocument('json');
  845. $groups = array();
  846. if (is_array($group)) {
  847. foreach ($group as $g) {
  848. $twitter_group = $this->twitterGroupArray($g);
  849. array_push($groups, $twitter_group);
  850. }
  851. } else {
  852. while ($group->fetch()) {
  853. $twitter_group = $this->twitterGroupArray($group);
  854. array_push($groups, $twitter_group);
  855. }
  856. }
  857. $this->showJsonObjects($groups);
  858. $this->endDocument('json');
  859. }
  860. function showXmlGroups($group)
  861. {
  862. $this->initDocument('xml');
  863. $this->elementStart('groups', array('type' => 'array'));
  864. if (is_array($group)) {
  865. foreach ($group as $g) {
  866. $twitter_group = $this->twitterGroupArray($g);
  867. $this->showTwitterXmlGroup($twitter_group);
  868. }
  869. } else {
  870. while ($group->fetch()) {
  871. $twitter_group = $this->twitterGroupArray($group);
  872. $this->showTwitterXmlGroup($twitter_group);
  873. }
  874. }
  875. $this->elementEnd('groups');
  876. $this->endDocument('xml');
  877. }
  878. function showXmlLists($list, $next_cursor=0, $prev_cursor=0)
  879. {
  880. $this->initDocument('xml');
  881. $this->elementStart('lists_list');
  882. $this->elementStart('lists', array('type' => 'array'));
  883. if (is_array($list)) {
  884. foreach ($list as $l) {
  885. $twitter_list = $this->twitterListArray($l);
  886. $this->showTwitterXmlList($twitter_list);
  887. }
  888. } else {
  889. while ($list->fetch()) {
  890. $twitter_list = $this->twitterListArray($list);
  891. $this->showTwitterXmlList($twitter_list);
  892. }
  893. }
  894. $this->elementEnd('lists');
  895. $this->element('next_cursor', null, $next_cursor);
  896. $this->element('previous_cursor', null, $prev_cursor);
  897. $this->elementEnd('lists_list');
  898. $this->endDocument('xml');
  899. }
  900. function showJsonLists($list, $next_cursor=0, $prev_cursor=0)
  901. {
  902. $this->initDocument('json');
  903. $lists = array();
  904. if (is_array($list)) {
  905. foreach ($list as $l) {
  906. $twitter_list = $this->twitterListArray($l);
  907. array_push($lists, $twitter_list);
  908. }
  909. } else {
  910. while ($list->fetch()) {
  911. $twitter_list = $this->twitterListArray($list);
  912. array_push($lists, $twitter_list);
  913. }
  914. }
  915. $lists_list = array(
  916. 'lists' => $lists,
  917. 'next_cursor' => $next_cursor,
  918. 'next_cursor_str' => strval($next_cursor),
  919. 'previous_cursor' => $prev_cursor,
  920. 'previous_cursor_str' => strval($prev_cursor)
  921. );
  922. $this->showJsonObjects($lists_list);
  923. $this->endDocument('json');
  924. }
  925. function showTwitterXmlUsers($user)
  926. {
  927. $this->initDocument('xml');
  928. $this->elementStart('users', array('type' => 'array',
  929. 'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
  930. if (is_array($user)) {
  931. foreach ($user as $u) {
  932. $twitter_user = $this->twitterUserArray($u);
  933. $this->showTwitterXmlUser($twitter_user);
  934. }
  935. } else {
  936. while ($user->fetch()) {
  937. $twitter_user = $this->twitterUserArray($user);
  938. $this->showTwitterXmlUser($twitter_user);
  939. }
  940. }
  941. $this->elementEnd('users');
  942. $this->endDocument('xml');
  943. }
  944. function showJsonUsers($user)
  945. {
  946. $this->initDocument('json');
  947. $users = array();
  948. if (is_array($user)) {
  949. foreach ($user as $u) {
  950. $twitter_user = $this->twitterUserArray($u);
  951. array_push($users, $twitter_user);
  952. }
  953. } else {
  954. while ($user->fetch()) {
  955. $twitter_user = $this->twitterUserArray($user);
  956. array_push($users, $twitter_user);
  957. }
  958. }
  959. $this->showJsonObjects($users);
  960. $this->endDocument('json');
  961. }
  962. function showSingleJsonGroup($group)
  963. {
  964. $this->initDocument('json');
  965. $twitter_group = $this->twitterGroupArray($group);
  966. $this->showJsonObjects($twitter_group);
  967. $this->endDocument('json');
  968. }
  969. function showSingleXmlGroup($group)
  970. {
  971. $this->initDocument('xml');
  972. $twitter_group = $this->twitterGroupArray($group);
  973. $this->showTwitterXmlGroup($twitter_group);
  974. $this->endDocument('xml');
  975. }
  976. function showSingleJsonList($list)
  977. {
  978. $this->initDocument('json');
  979. $twitter_list = $this->twitterListArray($list);
  980. $this->showJsonObjects($twitter_list);
  981. $this->endDocument('json');
  982. }
  983. function showSingleXmlList($list)
  984. {
  985. $this->initDocument('xml');
  986. $twitter_list = $this->twitterListArray($list);
  987. $this->showTwitterXmlList($twitter_list);
  988. $this->endDocument('xml');
  989. }
  990. function dateTwitter($dt)
  991. {
  992. $dateStr = date('d F Y H:i:s', strtotime($dt));
  993. $d = new DateTime($dateStr, new DateTimeZone('UTC'));
  994. $d->setTimezone(new DateTimeZone(common_timezone()));
  995. return $d->format('D M d H:i:s O Y');
  996. }
  997. function initDocument($type='xml')
  998. {
  999. switch ($type) {
  1000. case 'xml':
  1001. header('Content-Type: application/xml; charset=utf-8');
  1002. $this->startXML();
  1003. break;
  1004. case 'json':
  1005. header('Content-Type: application/json; charset=utf-8');
  1006. // Check for JSONP callback
  1007. if (isset($this->callback)) {
  1008. print $this->callback . '(';
  1009. }
  1010. break;
  1011. case 'rss':
  1012. header("Content-Type: application/rss+xml; charset=utf-8");
  1013. $this->initTwitterRss();
  1014. break;
  1015. case 'atom':
  1016. header('Content-Type: application/atom+xml; charset=utf-8');
  1017. $this->initTwitterAtom();
  1018. break;
  1019. default:
  1020. // TRANS: Client error on an API request with an unsupported data format.
  1021. $this->clientError(_('Not a supported data format.'));
  1022. }
  1023. return;
  1024. }
  1025. function endDocument($type='xml')
  1026. {
  1027. switch ($type) {
  1028. case 'xml':
  1029. $this->endXML();
  1030. break;
  1031. case 'json':
  1032. // Check for JSONP callback
  1033. if (isset($this->callback)) {
  1034. print ')';
  1035. }
  1036. break;
  1037. case 'rss':
  1038. $this->endTwitterRss();
  1039. break;
  1040. case 'atom':
  1041. $this->endTwitterRss();
  1042. break;
  1043. default:
  1044. // TRANS: Client error on an API request with an unsupported data format.
  1045. $this->clientError(_('Not a supported data format.'));
  1046. }
  1047. return;
  1048. }
  1049. function initTwitterRss()
  1050. {
  1051. $this->startXML();
  1052. $this->elementStart(
  1053. 'rss',
  1054. array(
  1055. 'version' => '2.0',
  1056. 'xmlns:atom' => 'http://www.w3.org/2005/Atom',
  1057. 'xmlns:georss' => 'http://www.georss.org/georss'
  1058. )
  1059. );
  1060. $this->elementStart('channel');
  1061. Event::handle('StartApiRss', array($this));
  1062. }
  1063. function endTwitterRss()
  1064. {
  1065. $this->elementEnd('channel');
  1066. $this->elementEnd('rss');
  1067. $this->endXML();
  1068. }
  1069. function initTwitterAtom()
  1070. {
  1071. $this->startXML();
  1072. // FIXME: don't hardcode the language here!
  1073. $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
  1074. 'xml:lang' => 'en-US',
  1075. 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
  1076. }
  1077. function endTwitterAtom()
  1078. {
  1079. $this->elementEnd('feed');
  1080. $this->endXML();
  1081. }
  1082. function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
  1083. {
  1084. $profile_array = $this->twitterUserArray($profile, $includeStatuses);
  1085. switch ($content_type) {
  1086. case 'xml':
  1087. $this->showTwitterXmlUser($profile_array);
  1088. break;
  1089. case 'json':
  1090. $this->showJsonObjects($profile_array);
  1091. break;
  1092. default:
  1093. // TRANS: Client error on an API request with an unsupported data format.
  1094. $this->clientError(_('Not a supported data format.'));
  1095. }
  1096. return;
  1097. }
  1098. private static function is_decimal($str)
  1099. {
  1100. return preg_match('/^[0-9]+$/', $str);
  1101. }
  1102. function getTargetUser($id)
  1103. {
  1104. if (empty($id)) {
  1105. // Twitter supports these other ways of passing the user ID
  1106. if (self::is_decimal($this->arg('id'))) {
  1107. return User::getKV($this->arg('id'));
  1108. } else if ($this->arg('id')) {
  1109. $nickname = common_canonical_nickname($this->arg('id'));
  1110. return User::getKV('nickname', $nickname);
  1111. } else if ($this->arg('user_id')) {
  1112. // This is to ensure that a non-numeric user_id still
  1113. // overrides screen_name even if it doesn't get used
  1114. if (self::is_decimal($this->arg('user_id'))) {
  1115. return User::getKV('id', $this->arg('user_id'));
  1116. }
  1117. } else if ($this->arg('screen_name')) {
  1118. $nickname = common_canonical_nickname($this->arg('screen_name'));
  1119. return User::getKV('nickname', $nickname);
  1120. } else {
  1121. // Fall back to trying the currently authenticated user
  1122. return $this->auth_user;
  1123. }
  1124. } else if (self::is_decimal($id)) {
  1125. return User::getKV($id);
  1126. } else {
  1127. $nickname = common_canonical_nickname($id);
  1128. return User::getKV('nickname', $nickname);
  1129. }
  1130. }
  1131. function getTargetProfile($id)
  1132. {
  1133. if (empty($id)) {
  1134. // Twitter supports these other ways of passing the user ID
  1135. if (self::is_decimal($this->arg('id'))) {
  1136. return Profile::getKV($this->arg('id'));
  1137. } else if ($this->arg('id')) {
  1138. // Screen names currently can only uniquely identify a local user.
  1139. $nickname = common_canonical_nickname($this->arg('id'));
  1140. $user = User::getKV('nickname', $nickname);
  1141. return $user ? $user->getProfile() : null;
  1142. } else if ($this->arg('user_id')) {
  1143. // This is to ensure that a non-numeric user_id still
  1144. // overrides screen_name even if it doesn't get used
  1145. if (self::is_decimal($this->arg('user_id'))) {
  1146. return Profile::getKV('id', $this->arg('user_id'));
  1147. }
  1148. } else if ($this->arg('screen_name')) {
  1149. $nickname = common_canonical_nickname($this->arg('screen_name'));
  1150. $user = User::getKV('nickname', $nickname);
  1151. return $user instanceof User ? $user->getProfile() : null;
  1152. } else {
  1153. // Fall back to trying the currently authenticated user
  1154. return $this->scoped;
  1155. }
  1156. } else if (self::is_decimal($id)) {
  1157. return Profile::getKV($id);
  1158. } else {
  1159. $nickname = common_canonical_nickname($id);
  1160. $user = User::getKV('nickname', $nickname);
  1161. return $user ? $user->getProfile() : null;
  1162. }
  1163. }
  1164. function getTargetGroup($id)
  1165. {
  1166. if (empty($id)) {
  1167. if (self::is_decimal($this->arg('id'))) {
  1168. return User_group::getKV('id', $this->arg('id'));
  1169. } else if ($this->arg('id')) {
  1170. return User_group::getForNickname($this->arg('id'));
  1171. } else if ($this->arg('group_id')) {
  1172. // This is to ensure that a non-numeric group_id still
  1173. // overrides group_name even if it doesn't get used
  1174. if (self::is_decimal($this->arg('group_id'))) {
  1175. return User_group::getKV('id', $this->arg('group_id'));
  1176. }
  1177. } else if ($this->arg('group_name')) {
  1178. return User_group::getForNickname($this->arg('group_name'));
  1179. }
  1180. } else if (self::is_decimal($id)) {
  1181. return User_group::getKV('id', $id);
  1182. } else if ($this->arg('uri')) { // FIXME: move this into empty($id) check?
  1183. return User_group::getKV('uri', urldecode($this->arg('uri')));
  1184. } else {
  1185. return User_group::getForNickname($id);
  1186. }
  1187. }
  1188. function getTargetList($user=null, $id=null)
  1189. {
  1190. $tagger = $this->getTargetUser($user);
  1191. $list = null;
  1192. if (empty($id)) {
  1193. $id = $this->arg('id');
  1194. }
  1195. if($id) {
  1196. if (is_numeric($id)) {
  1197. $list = Profile_list::getKV('id', $id);
  1198. // only if the list with the id belongs to the tagger
  1199. if(empty($list) || $list->tagger != $tagger->id) {
  1200. $list = null;
  1201. }
  1202. }
  1203. if (empty($list)) {
  1204. $tag = common_canonical_tag($id);
  1205. $list = Profile_list::getByTaggerAndTag($tagger->id, $tag);
  1206. }
  1207. if (!empty($list) && $list->private) {
  1208. if ($this->auth_user->id == $list->tagger) {
  1209. return $list;
  1210. }
  1211. } else {
  1212. return $list;
  1213. }
  1214. }
  1215. return null;
  1216. }
  1217. /**
  1218. * Returns query argument or default value if not found. Certain
  1219. * parameters used throughout the API are lightly scrubbed and
  1220. * bounds checked. This overrides Action::arg().
  1221. *
  1222. * @param string $key requested argument
  1223. * @param string $def default value to return if $key is not provided
  1224. *
  1225. * @return var $var
  1226. */
  1227. function arg($key, $def=null)
  1228. {
  1229. // XXX: Do even more input validation/scrubbing?
  1230. if (array_key_exists($key, $this->args)) {
  1231. switch($key) {
  1232. case 'page':
  1233. $page = (int)$this->args['page'];
  1234. return ($page < 1) ? 1 : $page;
  1235. case 'count':
  1236. $count = (int)$this->args['count'];
  1237. if ($count < 1) {
  1238. return 20;
  1239. } elseif ($count > 200) {
  1240. return 200;
  1241. } else {
  1242. return $count;
  1243. }
  1244. case 'since_id':
  1245. $since_id = (int)$this->args['since_id'];
  1246. return ($since_id < 1) ? 0 : $since_id;
  1247. case 'max_id':
  1248. $max_id = (int)$this->args['max_id'];
  1249. return ($max_id < 1) ? 0 : $max_id;
  1250. default:
  1251. return parent::arg($key, $def);
  1252. }
  1253. } else {
  1254. return $def;
  1255. }
  1256. }
  1257. /**
  1258. * Calculate the complete URI that called up this action. Used for
  1259. * Atom rel="self" links. Warning: this is funky.
  1260. *
  1261. * @return string URL a URL suitable for rel="self" Atom links
  1262. */
  1263. function getSelfUri()
  1264. {
  1265. $action = mb_substr(get_class($this), 0, -6); // remove 'Action'
  1266. $id = $this->arg('id');
  1267. $aargs = array('format' => $this->format);
  1268. if (!empty($id)) {
  1269. $aargs['id'] = $id;
  1270. }
  1271. $tag = $this->arg('tag');
  1272. if (!empty($tag)) {
  1273. $aargs['tag'] = $tag;
  1274. }
  1275. parse_str($_SERVER['QUERY_STRING'], $params);
  1276. $pstring = '';
  1277. if (!empty($params)) {
  1278. unset($params['p']);
  1279. $pstring = http_build_query($params);
  1280. }
  1281. $uri = common_local_url($action, $aargs);
  1282. if (!empty($pstring)) {
  1283. $uri .= '?' . $pstring;
  1284. }
  1285. return $uri;
  1286. }
  1287. }