apiaction.php 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532
  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. GNUsocial::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-GNUsocial-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. // These values might actually also mean "unknown". Ambiguity issues?
  210. $twitter_user['following'] = false;
  211. $twitter_user['statusnet_blocking'] = false;
  212. $twitter_user['notifications'] = false;
  213. if ($this->scoped instanceof Profile) {
  214. try {
  215. $sub = Subscription::getSubscription($this->scoped, $profile);
  216. // Notifications on?
  217. $twitter_user['following'] = true;
  218. $twitter_user['statusnet_blocking'] = $this->scoped->hasBlocked($profile);
  219. $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
  220. } catch (NoResultException $e) {
  221. // well, the values are already false...
  222. }
  223. }
  224. if ($get_notice) {
  225. $notice = $profile->getCurrentNotice();
  226. if ($notice instanceof Notice) {
  227. // don't get user!
  228. $twitter_user['status'] = $this->twitterStatusArray($notice, false);
  229. }
  230. }
  231. // StatusNet-specific
  232. $twitter_user['statusnet_profile_url'] = $profile->profileurl;
  233. // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array
  234. Event::handle('TwitterUserArray', array($profile, &$twitter_user, $this->scoped, array()));
  235. return $twitter_user;
  236. }
  237. function twitterStatusArray($notice, $include_user=true)
  238. {
  239. $base = $this->twitterSimpleStatusArray($notice, $include_user);
  240. // FIXME: MOVE TO SHARE PLUGIN
  241. if (!empty($notice->repeat_of)) {
  242. $original = Notice::getKV('id', $notice->repeat_of);
  243. if ($original instanceof Notice) {
  244. $orig_array = $this->twitterSimpleStatusArray($original, $include_user);
  245. $base['retweeted_status'] = $orig_array;
  246. }
  247. }
  248. return $base;
  249. }
  250. function twitterSimpleStatusArray($notice, $include_user=true)
  251. {
  252. $profile = $notice->getProfile();
  253. $twitter_status = array();
  254. $twitter_status['text'] = $notice->content;
  255. $twitter_status['truncated'] = false; # Not possible on StatusNet
  256. $twitter_status['created_at'] = $this->dateTwitter($notice->created);
  257. try {
  258. // We could just do $notice->reply_to but maybe the future holds a
  259. // different story for parenting.
  260. $parent = $notice->getParent();
  261. $in_reply_to = $parent->id;
  262. } catch (Exception $e) {
  263. $in_reply_to = null;
  264. }
  265. $twitter_status['in_reply_to_status_id'] = $in_reply_to;
  266. $source = null;
  267. $ns = $notice->getSource();
  268. if ($ns instanceof Notice_source) {
  269. if (!empty($ns->name) && !empty($ns->url)) {
  270. $source = '<a href="'
  271. . htmlspecialchars($ns->url)
  272. . '" rel="nofollow">'
  273. . htmlspecialchars($ns->name)
  274. . '</a>';
  275. } else {
  276. $source = $ns->code;
  277. }
  278. }
  279. $twitter_status['uri'] = $notice->getUri();
  280. $twitter_status['source'] = $source;
  281. $twitter_status['id'] = intval($notice->id);
  282. $replier_profile = null;
  283. if ($notice->reply_to) {
  284. $reply = Notice::getKV(intval($notice->reply_to));
  285. if ($reply) {
  286. $replier_profile = $reply->getProfile();
  287. }
  288. }
  289. $twitter_status['in_reply_to_user_id'] =
  290. ($replier_profile) ? intval($replier_profile->id) : null;
  291. $twitter_status['in_reply_to_screen_name'] =
  292. ($replier_profile) ? $replier_profile->nickname : null;
  293. if (isset($notice->lat) && isset($notice->lon)) {
  294. // This is the format that GeoJSON expects stuff to be in
  295. $twitter_status['geo'] = array('type' => 'Point',
  296. 'coordinates' => array((float) $notice->lat,
  297. (float) $notice->lon));
  298. } else {
  299. $twitter_status['geo'] = null;
  300. }
  301. // Enclosures
  302. $attachments = $notice->attachments();
  303. if (!empty($attachments)) {
  304. $twitter_status['attachments'] = array();
  305. foreach ($attachments as $attachment) {
  306. try {
  307. $enclosure_o = $attachment->getEnclosure();
  308. $enclosure = array();
  309. $enclosure['url'] = $enclosure_o->url;
  310. $enclosure['mimetype'] = $enclosure_o->mimetype;
  311. $enclosure['size'] = $enclosure_o->size;
  312. $twitter_status['attachments'][] = $enclosure;
  313. } catch (ServerException $e) {
  314. // There was not enough metadata available
  315. }
  316. }
  317. }
  318. if ($include_user && $profile) {
  319. // Don't get notice (recursive!)
  320. $twitter_user = $this->twitterUserArray($profile, false);
  321. $twitter_status['user'] = $twitter_user;
  322. }
  323. // StatusNet-specific
  324. $twitter_status['statusnet_html'] = $notice->rendered;
  325. $twitter_status['statusnet_conversation_id'] = intval($notice->conversation);
  326. // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array
  327. Event::handle('NoticeSimpleStatusArray', array($notice, &$twitter_status, $this->scoped,
  328. array('include_user'=>$include_user)));
  329. return $twitter_status;
  330. }
  331. function twitterGroupArray($group)
  332. {
  333. $twitter_group = array();
  334. $twitter_group['id'] = intval($group->id);
  335. $twitter_group['url'] = $group->permalink();
  336. $twitter_group['nickname'] = $group->nickname;
  337. $twitter_group['fullname'] = $group->fullname;
  338. if ($this->scoped instanceof Profile) {
  339. $twitter_group['member'] = $this->scoped->isMember($group);
  340. $twitter_group['blocked'] = Group_block::isBlocked(
  341. $group,
  342. $this->scoped
  343. );
  344. }
  345. $twitter_group['admin_count'] = $group->getAdminCount();
  346. $twitter_group['member_count'] = $group->getMemberCount();
  347. $twitter_group['original_logo'] = $group->original_logo;
  348. $twitter_group['homepage_logo'] = $group->homepage_logo;
  349. $twitter_group['stream_logo'] = $group->stream_logo;
  350. $twitter_group['mini_logo'] = $group->mini_logo;
  351. $twitter_group['homepage'] = $group->homepage;
  352. $twitter_group['description'] = $group->description;
  353. $twitter_group['location'] = $group->location;
  354. $twitter_group['created'] = $this->dateTwitter($group->created);
  355. $twitter_group['modified'] = $this->dateTwitter($group->modified);
  356. return $twitter_group;
  357. }
  358. function twitterRssGroupArray($group)
  359. {
  360. $entry = array();
  361. $entry['content']=$group->description;
  362. $entry['title']=$group->nickname;
  363. $entry['link']=$group->permalink();
  364. $entry['published']=common_date_iso8601($group->created);
  365. $entry['updated']==common_date_iso8601($group->modified);
  366. $taguribase = common_config('integration', 'groupuri');
  367. $entry['id'] = "group:$groupuribase:$entry[link]";
  368. $entry['description'] = $entry['content'];
  369. $entry['pubDate'] = common_date_rfc2822($group->created);
  370. $entry['guid'] = $entry['link'];
  371. return $entry;
  372. }
  373. function twitterListArray($list)
  374. {
  375. $profile = Profile::getKV('id', $list->tagger);
  376. $twitter_list = array();
  377. $twitter_list['id'] = $list->id;
  378. $twitter_list['name'] = $list->tag;
  379. $twitter_list['full_name'] = '@'.$profile->nickname.'/'.$list->tag;;
  380. $twitter_list['slug'] = $list->tag;
  381. $twitter_list['description'] = $list->description;
  382. $twitter_list['subscriber_count'] = $list->subscriberCount();
  383. $twitter_list['member_count'] = $list->taggedCount();
  384. $twitter_list['uri'] = $list->getUri();
  385. if ($this->scoped instanceof Profile) {
  386. $twitter_list['following'] = $list->hasSubscriber($this->scoped);
  387. } else {
  388. $twitter_list['following'] = false;
  389. }
  390. $twitter_list['mode'] = ($list->private) ? 'private' : 'public';
  391. $twitter_list['user'] = $this->twitterUserArray($profile, false);
  392. return $twitter_list;
  393. }
  394. function twitterRssEntryArray($notice)
  395. {
  396. $entry = array();
  397. if (Event::handle('StartRssEntryArray', array($notice, &$entry))) {
  398. $profile = $notice->getProfile();
  399. // We trim() to avoid extraneous whitespace in the output
  400. $entry['content'] = common_xml_safe_str(trim($notice->rendered));
  401. $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
  402. $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
  403. $entry['published'] = common_date_iso8601($notice->created);
  404. $taguribase = TagURI::base();
  405. $entry['id'] = "tag:$taguribase:$entry[link]";
  406. $entry['updated'] = $entry['published'];
  407. $entry['author'] = $profile->getBestName();
  408. // Enclosures
  409. $attachments = $notice->attachments();
  410. $enclosures = array();
  411. foreach ($attachments as $attachment) {
  412. try {
  413. $enclosure_o = $attachment->getEnclosure();
  414. $enclosure = array();
  415. $enclosure['url'] = $enclosure_o->url;
  416. $enclosure['mimetype'] = $enclosure_o->mimetype;
  417. $enclosure['size'] = $enclosure_o->size;
  418. $enclosures[] = $enclosure;
  419. } catch (ServerException $e) {
  420. // There was not enough metadata available
  421. }
  422. }
  423. if (!empty($enclosures)) {
  424. $entry['enclosures'] = $enclosures;
  425. }
  426. // Tags/Categories
  427. $tag = new Notice_tag();
  428. $tag->notice_id = $notice->id;
  429. if ($tag->find()) {
  430. $entry['tags']=array();
  431. while ($tag->fetch()) {
  432. $entry['tags'][]=$tag->tag;
  433. }
  434. }
  435. $tag->free();
  436. // RSS Item specific
  437. $entry['description'] = $entry['content'];
  438. $entry['pubDate'] = common_date_rfc2822($notice->created);
  439. $entry['guid'] = $entry['link'];
  440. if (isset($notice->lat) && isset($notice->lon)) {
  441. // This is the format that GeoJSON expects stuff to be in.
  442. // showGeoRSS() below uses it for XML output, so we reuse it
  443. $entry['geo'] = array('type' => 'Point',
  444. 'coordinates' => array((float) $notice->lat,
  445. (float) $notice->lon));
  446. } else {
  447. $entry['geo'] = null;
  448. }
  449. Event::handle('EndRssEntryArray', array($notice, &$entry));
  450. }
  451. return $entry;
  452. }
  453. function twitterRelationshipArray($source, $target)
  454. {
  455. $relationship = array();
  456. $relationship['source'] =
  457. $this->relationshipDetailsArray($source->getProfile(), $target->getProfile());
  458. $relationship['target'] =
  459. $this->relationshipDetailsArray($target->getProfile(), $source->getProfile());
  460. return array('relationship' => $relationship);
  461. }
  462. function relationshipDetailsArray(Profile $source, Profile $target)
  463. {
  464. $details = array();
  465. $details['screen_name'] = $source->getNickname();
  466. $details['followed_by'] = $target->isSubscribed($source);
  467. try {
  468. $sub = Subscription::getSubscription($source, $target);
  469. $details['following'] = true;
  470. $details['notifications_enabled'] = ($sub->jabber || $sub->sms);
  471. } catch (NoResultException $e) {
  472. $details['following'] = false;
  473. $details['notifications_enabled'] = false;
  474. }
  475. $details['blocking'] = $source->hasBlocked($target);
  476. $details['id'] = intval($source->id);
  477. return $details;
  478. }
  479. function showTwitterXmlRelationship($relationship)
  480. {
  481. $this->elementStart('relationship');
  482. foreach($relationship as $element => $value) {
  483. if ($element == 'source' || $element == 'target') {
  484. $this->elementStart($element);
  485. $this->showXmlRelationshipDetails($value);
  486. $this->elementEnd($element);
  487. }
  488. }
  489. $this->elementEnd('relationship');
  490. }
  491. function showXmlRelationshipDetails($details)
  492. {
  493. foreach($details as $element => $value) {
  494. $this->element($element, null, $value);
  495. }
  496. }
  497. function showTwitterXmlStatus($twitter_status, $tag='status', $namespaces=false)
  498. {
  499. $attrs = array();
  500. if ($namespaces) {
  501. $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
  502. }
  503. $this->elementStart($tag, $attrs);
  504. foreach($twitter_status as $element => $value) {
  505. switch ($element) {
  506. case 'user':
  507. $this->showTwitterXmlUser($twitter_status['user']);
  508. break;
  509. case 'text':
  510. $this->element($element, null, common_xml_safe_str($value));
  511. break;
  512. case 'attachments':
  513. $this->showXmlAttachments($twitter_status['attachments']);
  514. break;
  515. case 'geo':
  516. $this->showGeoXML($value);
  517. break;
  518. case 'retweeted_status':
  519. // FIXME: MOVE TO SHARE PLUGIN
  520. $this->showTwitterXmlStatus($value, 'retweeted_status');
  521. break;
  522. default:
  523. if (strncmp($element, 'statusnet_', 10) == 0) {
  524. $this->element('statusnet:'.substr($element, 10), null, $value);
  525. } else {
  526. $this->element($element, null, $value);
  527. }
  528. }
  529. }
  530. $this->elementEnd($tag);
  531. }
  532. function showTwitterXmlGroup($twitter_group)
  533. {
  534. $this->elementStart('group');
  535. foreach($twitter_group as $element => $value) {
  536. $this->element($element, null, $value);
  537. }
  538. $this->elementEnd('group');
  539. }
  540. function showTwitterXmlList($twitter_list)
  541. {
  542. $this->elementStart('list');
  543. foreach($twitter_list as $element => $value) {
  544. if($element == 'user') {
  545. $this->showTwitterXmlUser($value, 'user');
  546. }
  547. else {
  548. $this->element($element, null, $value);
  549. }
  550. }
  551. $this->elementEnd('list');
  552. }
  553. function showTwitterXmlUser($twitter_user, $role='user', $namespaces=false)
  554. {
  555. $attrs = array();
  556. if ($namespaces) {
  557. $attrs['xmlns:statusnet'] = 'http://status.net/schema/api/1/';
  558. }
  559. $this->elementStart($role, $attrs);
  560. foreach($twitter_user as $element => $value) {
  561. if ($element == 'status') {
  562. $this->showTwitterXmlStatus($twitter_user['status']);
  563. } else if (strncmp($element, 'statusnet_', 10) == 0) {
  564. $this->element('statusnet:'.substr($element, 10), null, $value);
  565. } else {
  566. $this->element($element, null, $value);
  567. }
  568. }
  569. $this->elementEnd($role);
  570. }
  571. function showXmlAttachments($attachments) {
  572. if (!empty($attachments)) {
  573. $this->elementStart('attachments', array('type' => 'array'));
  574. foreach ($attachments as $attachment) {
  575. $attrs = array();
  576. $attrs['url'] = $attachment['url'];
  577. $attrs['mimetype'] = $attachment['mimetype'];
  578. $attrs['size'] = $attachment['size'];
  579. $this->element('enclosure', $attrs, '');
  580. }
  581. $this->elementEnd('attachments');
  582. }
  583. }
  584. function showGeoXML($geo)
  585. {
  586. if (empty($geo)) {
  587. // empty geo element
  588. $this->element('geo');
  589. } else {
  590. $this->elementStart('geo', array('xmlns:georss' => 'http://www.georss.org/georss'));
  591. $this->element('georss:point', null, $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]);
  592. $this->elementEnd('geo');
  593. }
  594. }
  595. function showGeoRSS($geo)
  596. {
  597. if (!empty($geo)) {
  598. $this->element(
  599. 'georss:point',
  600. null,
  601. $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]
  602. );
  603. }
  604. }
  605. function showTwitterRssItem($entry)
  606. {
  607. $this->elementStart('item');
  608. $this->element('title', null, $entry['title']);
  609. $this->element('description', null, $entry['description']);
  610. $this->element('pubDate', null, $entry['pubDate']);
  611. $this->element('guid', null, $entry['guid']);
  612. $this->element('link', null, $entry['link']);
  613. // RSS only supports 1 enclosure per item
  614. if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
  615. $enclosure = $entry['enclosures'][0];
  616. $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
  617. }
  618. if(array_key_exists('tags', $entry)){
  619. foreach($entry['tags'] as $tag){
  620. $this->element('category', null,$tag);
  621. }
  622. }
  623. $this->showGeoRSS($entry['geo']);
  624. $this->elementEnd('item');
  625. }
  626. function showJsonObjects($objects)
  627. {
  628. print(json_encode($objects));
  629. }
  630. function showSingleXmlStatus($notice)
  631. {
  632. $this->initDocument('xml');
  633. $twitter_status = $this->twitterStatusArray($notice);
  634. $this->showTwitterXmlStatus($twitter_status, 'status', true);
  635. $this->endDocument('xml');
  636. }
  637. function showSingleAtomStatus($notice)
  638. {
  639. header('Content-Type: application/atom+xml; charset=utf-8');
  640. print $notice->asAtomEntry(true, true, true, $this->scoped);
  641. }
  642. function show_single_json_status($notice)
  643. {
  644. $this->initDocument('json');
  645. $status = $this->twitterStatusArray($notice);
  646. $this->showJsonObjects($status);
  647. $this->endDocument('json');
  648. }
  649. function showXmlTimeline($notice)
  650. {
  651. $this->initDocument('xml');
  652. $this->elementStart('statuses', array('type' => 'array',
  653. 'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
  654. if (is_array($notice)) {
  655. $notice = new ArrayWrapper($notice);
  656. }
  657. while ($notice->fetch()) {
  658. try {
  659. $twitter_status = $this->twitterStatusArray($notice);
  660. $this->showTwitterXmlStatus($twitter_status);
  661. } catch (Exception $e) {
  662. common_log(LOG_ERR, $e->getMessage());
  663. continue;
  664. }
  665. }
  666. $this->elementEnd('statuses');
  667. $this->endDocument('xml');
  668. }
  669. function showRssTimeline($notice, $title, $link, $subtitle, $suplink = null, $logo = null, $self = null)
  670. {
  671. $this->initDocument('rss');
  672. $this->element('title', null, $title);
  673. $this->element('link', null, $link);
  674. if (!is_null($self)) {
  675. $this->element(
  676. 'atom:link',
  677. array(
  678. 'type' => 'application/rss+xml',
  679. 'href' => $self,
  680. 'rel' => 'self'
  681. )
  682. );
  683. }
  684. if (!is_null($suplink)) {
  685. // For FriendFeed's SUP protocol
  686. $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
  687. 'rel' => 'http://api.friendfeed.com/2008/03#sup',
  688. 'href' => $suplink,
  689. 'type' => 'application/json'));
  690. }
  691. if (!is_null($logo)) {
  692. $this->elementStart('image');
  693. $this->element('link', null, $link);
  694. $this->element('title', null, $title);
  695. $this->element('url', null, $logo);
  696. $this->elementEnd('image');
  697. }
  698. $this->element('description', null, $subtitle);
  699. $this->element('language', null, 'en-us');
  700. $this->element('ttl', null, '40');
  701. if (is_array($notice)) {
  702. $notice = new ArrayWrapper($notice);
  703. }
  704. while ($notice->fetch()) {
  705. try {
  706. $entry = $this->twitterRssEntryArray($notice);
  707. $this->showTwitterRssItem($entry);
  708. } catch (Exception $e) {
  709. common_log(LOG_ERR, $e->getMessage());
  710. // continue on exceptions
  711. }
  712. }
  713. $this->endTwitterRss();
  714. }
  715. function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null, $logo=null)
  716. {
  717. $this->initDocument('atom');
  718. $this->element('title', null, $title);
  719. $this->element('id', null, $id);
  720. $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
  721. if (!is_null($logo)) {
  722. $this->element('logo',null,$logo);
  723. }
  724. if (!is_null($suplink)) {
  725. // For FriendFeed's SUP protocol
  726. $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
  727. 'href' => $suplink,
  728. 'type' => 'application/json'));
  729. }
  730. if (!is_null($selfuri)) {
  731. $this->element('link', array('href' => $selfuri,
  732. 'rel' => 'self', 'type' => 'application/atom+xml'), null);
  733. }
  734. $this->element('updated', null, common_date_iso8601('now'));
  735. $this->element('subtitle', null, $subtitle);
  736. if (is_array($notice)) {
  737. $notice = new ArrayWrapper($notice);
  738. }
  739. while ($notice->fetch()) {
  740. try {
  741. $this->raw($notice->asAtomEntry());
  742. } catch (Exception $e) {
  743. common_log(LOG_ERR, $e->getMessage());
  744. continue;
  745. }
  746. }
  747. $this->endDocument('atom');
  748. }
  749. function showRssGroups($group, $title, $link, $subtitle)
  750. {
  751. $this->initDocument('rss');
  752. $this->element('title', null, $title);
  753. $this->element('link', null, $link);
  754. $this->element('description', null, $subtitle);
  755. $this->element('language', null, 'en-us');
  756. $this->element('ttl', null, '40');
  757. if (is_array($group)) {
  758. foreach ($group as $g) {
  759. $twitter_group = $this->twitterRssGroupArray($g);
  760. $this->showTwitterRssItem($twitter_group);
  761. }
  762. } else {
  763. while ($group->fetch()) {
  764. $twitter_group = $this->twitterRssGroupArray($group);
  765. $this->showTwitterRssItem($twitter_group);
  766. }
  767. }
  768. $this->endTwitterRss();
  769. }
  770. function showTwitterAtomEntry($entry)
  771. {
  772. $this->elementStart('entry');
  773. $this->element('title', null, common_xml_safe_str($entry['title']));
  774. $this->element(
  775. 'content',
  776. array('type' => 'html'),
  777. common_xml_safe_str($entry['content'])
  778. );
  779. $this->element('id', null, $entry['id']);
  780. $this->element('published', null, $entry['published']);
  781. $this->element('updated', null, $entry['updated']);
  782. $this->element('link', array('type' => 'text/html',
  783. 'href' => $entry['link'],
  784. 'rel' => 'alternate'));
  785. $this->element('link', array('type' => $entry['avatar-type'],
  786. 'href' => $entry['avatar'],
  787. 'rel' => 'image'));
  788. $this->elementStart('author');
  789. $this->element('name', null, $entry['author-name']);
  790. $this->element('uri', null, $entry['author-uri']);
  791. $this->elementEnd('author');
  792. $this->elementEnd('entry');
  793. }
  794. function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
  795. {
  796. $this->initDocument('atom');
  797. $this->element('title', null, common_xml_safe_str($title));
  798. $this->element('id', null, $id);
  799. $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
  800. if (!is_null($selfuri)) {
  801. $this->element('link', array('href' => $selfuri,
  802. 'rel' => 'self', 'type' => 'application/atom+xml'), null);
  803. }
  804. $this->element('updated', null, common_date_iso8601('now'));
  805. $this->element('subtitle', null, common_xml_safe_str($subtitle));
  806. if (is_array($group)) {
  807. foreach ($group as $g) {
  808. $this->raw($g->asAtomEntry());
  809. }
  810. } else {
  811. while ($group->fetch()) {
  812. $this->raw($group->asAtomEntry());
  813. }
  814. }
  815. $this->endDocument('atom');
  816. }
  817. function showJsonTimeline($notice)
  818. {
  819. $this->initDocument('json');
  820. $statuses = array();
  821. if (is_array($notice)) {
  822. $notice = new ArrayWrapper($notice);
  823. }
  824. while ($notice->fetch()) {
  825. try {
  826. $twitter_status = $this->twitterStatusArray($notice);
  827. array_push($statuses, $twitter_status);
  828. } catch (Exception $e) {
  829. common_log(LOG_ERR, $e->getMessage());
  830. continue;
  831. }
  832. }
  833. $this->showJsonObjects($statuses);
  834. $this->endDocument('json');
  835. }
  836. function showJsonGroups($group)
  837. {
  838. $this->initDocument('json');
  839. $groups = array();
  840. if (is_array($group)) {
  841. foreach ($group as $g) {
  842. $twitter_group = $this->twitterGroupArray($g);
  843. array_push($groups, $twitter_group);
  844. }
  845. } else {
  846. while ($group->fetch()) {
  847. $twitter_group = $this->twitterGroupArray($group);
  848. array_push($groups, $twitter_group);
  849. }
  850. }
  851. $this->showJsonObjects($groups);
  852. $this->endDocument('json');
  853. }
  854. function showXmlGroups($group)
  855. {
  856. $this->initDocument('xml');
  857. $this->elementStart('groups', array('type' => 'array'));
  858. if (is_array($group)) {
  859. foreach ($group as $g) {
  860. $twitter_group = $this->twitterGroupArray($g);
  861. $this->showTwitterXmlGroup($twitter_group);
  862. }
  863. } else {
  864. while ($group->fetch()) {
  865. $twitter_group = $this->twitterGroupArray($group);
  866. $this->showTwitterXmlGroup($twitter_group);
  867. }
  868. }
  869. $this->elementEnd('groups');
  870. $this->endDocument('xml');
  871. }
  872. function showXmlLists($list, $next_cursor=0, $prev_cursor=0)
  873. {
  874. $this->initDocument('xml');
  875. $this->elementStart('lists_list');
  876. $this->elementStart('lists', array('type' => 'array'));
  877. if (is_array($list)) {
  878. foreach ($list as $l) {
  879. $twitter_list = $this->twitterListArray($l);
  880. $this->showTwitterXmlList($twitter_list);
  881. }
  882. } else {
  883. while ($list->fetch()) {
  884. $twitter_list = $this->twitterListArray($list);
  885. $this->showTwitterXmlList($twitter_list);
  886. }
  887. }
  888. $this->elementEnd('lists');
  889. $this->element('next_cursor', null, $next_cursor);
  890. $this->element('previous_cursor', null, $prev_cursor);
  891. $this->elementEnd('lists_list');
  892. $this->endDocument('xml');
  893. }
  894. function showJsonLists($list, $next_cursor=0, $prev_cursor=0)
  895. {
  896. $this->initDocument('json');
  897. $lists = array();
  898. if (is_array($list)) {
  899. foreach ($list as $l) {
  900. $twitter_list = $this->twitterListArray($l);
  901. array_push($lists, $twitter_list);
  902. }
  903. } else {
  904. while ($list->fetch()) {
  905. $twitter_list = $this->twitterListArray($list);
  906. array_push($lists, $twitter_list);
  907. }
  908. }
  909. $lists_list = array(
  910. 'lists' => $lists,
  911. 'next_cursor' => $next_cursor,
  912. 'next_cursor_str' => strval($next_cursor),
  913. 'previous_cursor' => $prev_cursor,
  914. 'previous_cursor_str' => strval($prev_cursor)
  915. );
  916. $this->showJsonObjects($lists_list);
  917. $this->endDocument('json');
  918. }
  919. function showTwitterXmlUsers($user)
  920. {
  921. $this->initDocument('xml');
  922. $this->elementStart('users', array('type' => 'array',
  923. 'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
  924. if (is_array($user)) {
  925. foreach ($user as $u) {
  926. $twitter_user = $this->twitterUserArray($u);
  927. $this->showTwitterXmlUser($twitter_user);
  928. }
  929. } else {
  930. while ($user->fetch()) {
  931. $twitter_user = $this->twitterUserArray($user);
  932. $this->showTwitterXmlUser($twitter_user);
  933. }
  934. }
  935. $this->elementEnd('users');
  936. $this->endDocument('xml');
  937. }
  938. function showJsonUsers($user)
  939. {
  940. $this->initDocument('json');
  941. $users = array();
  942. if (is_array($user)) {
  943. foreach ($user as $u) {
  944. $twitter_user = $this->twitterUserArray($u);
  945. array_push($users, $twitter_user);
  946. }
  947. } else {
  948. while ($user->fetch()) {
  949. $twitter_user = $this->twitterUserArray($user);
  950. array_push($users, $twitter_user);
  951. }
  952. }
  953. $this->showJsonObjects($users);
  954. $this->endDocument('json');
  955. }
  956. function showSingleJsonGroup($group)
  957. {
  958. $this->initDocument('json');
  959. $twitter_group = $this->twitterGroupArray($group);
  960. $this->showJsonObjects($twitter_group);
  961. $this->endDocument('json');
  962. }
  963. function showSingleXmlGroup($group)
  964. {
  965. $this->initDocument('xml');
  966. $twitter_group = $this->twitterGroupArray($group);
  967. $this->showTwitterXmlGroup($twitter_group);
  968. $this->endDocument('xml');
  969. }
  970. function showSingleJsonList($list)
  971. {
  972. $this->initDocument('json');
  973. $twitter_list = $this->twitterListArray($list);
  974. $this->showJsonObjects($twitter_list);
  975. $this->endDocument('json');
  976. }
  977. function showSingleXmlList($list)
  978. {
  979. $this->initDocument('xml');
  980. $twitter_list = $this->twitterListArray($list);
  981. $this->showTwitterXmlList($twitter_list);
  982. $this->endDocument('xml');
  983. }
  984. function dateTwitter($dt)
  985. {
  986. $dateStr = date('d F Y H:i:s', strtotime($dt));
  987. $d = new DateTime($dateStr, new DateTimeZone('UTC'));
  988. $d->setTimezone(new DateTimeZone(common_timezone()));
  989. return $d->format('D M d H:i:s O Y');
  990. }
  991. function initDocument($type='xml')
  992. {
  993. switch ($type) {
  994. case 'xml':
  995. header('Content-Type: application/xml; charset=utf-8');
  996. $this->startXML();
  997. break;
  998. case 'json':
  999. header('Content-Type: application/json; charset=utf-8');
  1000. // Check for JSONP callback
  1001. if (isset($this->callback)) {
  1002. print $this->callback . '(';
  1003. }
  1004. break;
  1005. case 'rss':
  1006. header("Content-Type: application/rss+xml; charset=utf-8");
  1007. $this->initTwitterRss();
  1008. break;
  1009. case 'atom':
  1010. header('Content-Type: application/atom+xml; charset=utf-8');
  1011. $this->initTwitterAtom();
  1012. break;
  1013. default:
  1014. // TRANS: Client error on an API request with an unsupported data format.
  1015. $this->clientError(_('Not a supported data format.'));
  1016. }
  1017. return;
  1018. }
  1019. function endDocument($type='xml')
  1020. {
  1021. switch ($type) {
  1022. case 'xml':
  1023. $this->endXML();
  1024. break;
  1025. case 'json':
  1026. // Check for JSONP callback
  1027. if (isset($this->callback)) {
  1028. print ')';
  1029. }
  1030. break;
  1031. case 'rss':
  1032. $this->endTwitterRss();
  1033. break;
  1034. case 'atom':
  1035. $this->endTwitterRss();
  1036. break;
  1037. default:
  1038. // TRANS: Client error on an API request with an unsupported data format.
  1039. $this->clientError(_('Not a supported data format.'));
  1040. }
  1041. return;
  1042. }
  1043. function initTwitterRss()
  1044. {
  1045. $this->startXML();
  1046. $this->elementStart(
  1047. 'rss',
  1048. array(
  1049. 'version' => '2.0',
  1050. 'xmlns:atom' => 'http://www.w3.org/2005/Atom',
  1051. 'xmlns:georss' => 'http://www.georss.org/georss'
  1052. )
  1053. );
  1054. $this->elementStart('channel');
  1055. Event::handle('StartApiRss', array($this));
  1056. }
  1057. function endTwitterRss()
  1058. {
  1059. $this->elementEnd('channel');
  1060. $this->elementEnd('rss');
  1061. $this->endXML();
  1062. }
  1063. function initTwitterAtom()
  1064. {
  1065. $this->startXML();
  1066. // FIXME: don't hardcode the language here!
  1067. $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
  1068. 'xml:lang' => 'en-US',
  1069. 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
  1070. }
  1071. function endTwitterAtom()
  1072. {
  1073. $this->elementEnd('feed');
  1074. $this->endXML();
  1075. }
  1076. function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
  1077. {
  1078. $profile_array = $this->twitterUserArray($profile, $includeStatuses);
  1079. switch ($content_type) {
  1080. case 'xml':
  1081. $this->showTwitterXmlUser($profile_array);
  1082. break;
  1083. case 'json':
  1084. $this->showJsonObjects($profile_array);
  1085. break;
  1086. default:
  1087. // TRANS: Client error on an API request with an unsupported data format.
  1088. $this->clientError(_('Not a supported data format.'));
  1089. }
  1090. return;
  1091. }
  1092. private static function is_decimal($str)
  1093. {
  1094. return preg_match('/^[0-9]+$/', $str);
  1095. }
  1096. function getTargetUser($id)
  1097. {
  1098. if (empty($id)) {
  1099. // Twitter supports these other ways of passing the user ID
  1100. if (self::is_decimal($this->arg('id'))) {
  1101. return User::getKV($this->arg('id'));
  1102. } else if ($this->arg('id')) {
  1103. $nickname = common_canonical_nickname($this->arg('id'));
  1104. return User::getKV('nickname', $nickname);
  1105. } else if ($this->arg('user_id')) {
  1106. // This is to ensure that a non-numeric user_id still
  1107. // overrides screen_name even if it doesn't get used
  1108. if (self::is_decimal($this->arg('user_id'))) {
  1109. return User::getKV('id', $this->arg('user_id'));
  1110. }
  1111. } else if ($this->arg('screen_name')) {
  1112. $nickname = common_canonical_nickname($this->arg('screen_name'));
  1113. return User::getKV('nickname', $nickname);
  1114. } else {
  1115. // Fall back to trying the currently authenticated user
  1116. return $this->scoped->getUser();
  1117. }
  1118. } else if (self::is_decimal($id)) {
  1119. return User::getKV($id);
  1120. } else {
  1121. $nickname = common_canonical_nickname($id);
  1122. return User::getKV('nickname', $nickname);
  1123. }
  1124. }
  1125. function getTargetProfile($id)
  1126. {
  1127. if (empty($id)) {
  1128. // Twitter supports these other ways of passing the user ID
  1129. if (self::is_decimal($this->arg('id'))) {
  1130. return Profile::getKV($this->arg('id'));
  1131. } else if ($this->arg('id')) {
  1132. // Screen names currently can only uniquely identify a local user.
  1133. $nickname = common_canonical_nickname($this->arg('id'));
  1134. $user = User::getKV('nickname', $nickname);
  1135. return $user ? $user->getProfile() : null;
  1136. } else if ($this->arg('user_id')) {
  1137. // This is to ensure that a non-numeric user_id still
  1138. // overrides screen_name even if it doesn't get used
  1139. if (self::is_decimal($this->arg('user_id'))) {
  1140. return Profile::getKV('id', $this->arg('user_id'));
  1141. }
  1142. } else if ($this->arg('screen_name')) {
  1143. $nickname = common_canonical_nickname($this->arg('screen_name'));
  1144. $user = User::getKV('nickname', $nickname);
  1145. return $user instanceof User ? $user->getProfile() : null;
  1146. } else {
  1147. // Fall back to trying the currently authenticated user
  1148. return $this->scoped;
  1149. }
  1150. } else if (self::is_decimal($id)) {
  1151. return Profile::getKV($id);
  1152. } else {
  1153. $nickname = common_canonical_nickname($id);
  1154. $user = User::getKV('nickname', $nickname);
  1155. return $user ? $user->getProfile() : null;
  1156. }
  1157. }
  1158. function getTargetGroup($id)
  1159. {
  1160. if (empty($id)) {
  1161. if (self::is_decimal($this->arg('id'))) {
  1162. return User_group::getKV('id', $this->arg('id'));
  1163. } else if ($this->arg('id')) {
  1164. return User_group::getForNickname($this->arg('id'));
  1165. } else if ($this->arg('group_id')) {
  1166. // This is to ensure that a non-numeric group_id still
  1167. // overrides group_name even if it doesn't get used
  1168. if (self::is_decimal($this->arg('group_id'))) {
  1169. return User_group::getKV('id', $this->arg('group_id'));
  1170. }
  1171. } else if ($this->arg('group_name')) {
  1172. return User_group::getForNickname($this->arg('group_name'));
  1173. }
  1174. } else if (self::is_decimal($id)) {
  1175. return User_group::getKV('id', $id);
  1176. } else if ($this->arg('uri')) { // FIXME: move this into empty($id) check?
  1177. return User_group::getKV('uri', urldecode($this->arg('uri')));
  1178. } else {
  1179. return User_group::getForNickname($id);
  1180. }
  1181. }
  1182. function getTargetList($user=null, $id=null)
  1183. {
  1184. $tagger = $this->getTargetUser($user);
  1185. $list = null;
  1186. if (empty($id)) {
  1187. $id = $this->arg('id');
  1188. }
  1189. if($id) {
  1190. if (is_numeric($id)) {
  1191. $list = Profile_list::getKV('id', $id);
  1192. // only if the list with the id belongs to the tagger
  1193. if(empty($list) || $list->tagger != $tagger->id) {
  1194. $list = null;
  1195. }
  1196. }
  1197. if (empty($list)) {
  1198. $tag = common_canonical_tag($id);
  1199. $list = Profile_list::getByTaggerAndTag($tagger->id, $tag);
  1200. }
  1201. if (!empty($list) && $list->private) {
  1202. if ($this->scoped->id == $list->tagger) {
  1203. return $list;
  1204. }
  1205. } else {
  1206. return $list;
  1207. }
  1208. }
  1209. return null;
  1210. }
  1211. /**
  1212. * Returns query argument or default value if not found. Certain
  1213. * parameters used throughout the API are lightly scrubbed and
  1214. * bounds checked. This overrides Action::arg().
  1215. *
  1216. * @param string $key requested argument
  1217. * @param string $def default value to return if $key is not provided
  1218. *
  1219. * @return var $var
  1220. */
  1221. function arg($key, $def=null)
  1222. {
  1223. // XXX: Do even more input validation/scrubbing?
  1224. if (array_key_exists($key, $this->args)) {
  1225. switch($key) {
  1226. case 'page':
  1227. $page = (int)$this->args['page'];
  1228. return ($page < 1) ? 1 : $page;
  1229. case 'count':
  1230. $count = (int)$this->args['count'];
  1231. if ($count < 1) {
  1232. return 20;
  1233. } elseif ($count > 200) {
  1234. return 200;
  1235. } else {
  1236. return $count;
  1237. }
  1238. case 'since_id':
  1239. $since_id = (int)$this->args['since_id'];
  1240. return ($since_id < 1) ? 0 : $since_id;
  1241. case 'max_id':
  1242. $max_id = (int)$this->args['max_id'];
  1243. return ($max_id < 1) ? 0 : $max_id;
  1244. default:
  1245. return parent::arg($key, $def);
  1246. }
  1247. } else {
  1248. return $def;
  1249. }
  1250. }
  1251. /**
  1252. * Calculate the complete URI that called up this action. Used for
  1253. * Atom rel="self" links. Warning: this is funky.
  1254. *
  1255. * @return string URL a URL suitable for rel="self" Atom links
  1256. */
  1257. function getSelfUri()
  1258. {
  1259. $action = mb_substr(get_class($this), 0, -6); // remove 'Action'
  1260. $id = $this->arg('id');
  1261. $aargs = array('format' => $this->format);
  1262. if (!empty($id)) {
  1263. $aargs['id'] = $id;
  1264. }
  1265. $user = $this->arg('user');
  1266. if (!empty($user)) {
  1267. $aargs['user'] = $user;
  1268. }
  1269. $tag = $this->arg('tag');
  1270. if (!empty($tag)) {
  1271. $aargs['tag'] = $tag;
  1272. }
  1273. parse_str($_SERVER['QUERY_STRING'], $params);
  1274. $pstring = '';
  1275. if (!empty($params)) {
  1276. unset($params['p']);
  1277. $pstring = http_build_query($params);
  1278. }
  1279. $uri = common_local_url($action, $aargs);
  1280. if (!empty($pstring)) {
  1281. $uri .= '?' . $pstring;
  1282. }
  1283. return $uri;
  1284. }
  1285. }