rsd.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <?php
  2. /**
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2008-2010, StatusNet, Inc.
  5. *
  6. * Really Simple Discovery (RSD) for API access
  7. *
  8. * PHP version 5
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. * @category API
  24. * @package StatusNet
  25. * @author Evan Prodromou <evan@status.net>
  26. * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
  27. * @link http://status.net/
  28. *
  29. */
  30. if (!defined('STATUSNET')) {
  31. exit(1);
  32. }
  33. /**
  34. * RSD action class
  35. *
  36. * Really Simple Discovery (RSD) is a simple (to a fault, maybe)
  37. * discovery tool for blog APIs.
  38. *
  39. * http://tales.phrasewise.com/rfc/rsd
  40. *
  41. * Anil Dash suggested that RSD be used for services that implement
  42. * the Twitter API:
  43. *
  44. * http://dashes.com/anil/2009/12/the-twitter-api-is-finished.html
  45. *
  46. * It's in use now for WordPress.com blogs:
  47. *
  48. * http://matt.wordpress.com/xmlrpc.php?rsd
  49. *
  50. * I (evan@status.net) have tried to stay faithful to the premise of
  51. * RSD, while adding information useful to StatusNet client developers.
  52. * In particular:
  53. *
  54. * - There is a link from each user's profile page to their personal
  55. * RSD feed. A personal rsd.xml includes a 'blogID' element that is
  56. * their username.
  57. * - There is a link from the public root to '/rsd.xml', a public RSD
  58. * feed. It's identical to the personal rsd except it doesn't include
  59. * a blogId.
  60. * - I've added a setting to the API to indicate that OAuth support is
  61. * available.
  62. *
  63. * @category API
  64. * @package StatusNet
  65. * @author Evan Prodromou <evan@status.net>
  66. * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
  67. * @link http://status.net/
  68. */
  69. class RsdAction extends Action
  70. {
  71. /**
  72. * Optional attribute for the personal rsd.xml file.
  73. */
  74. var $user = null;
  75. /**
  76. * Prepare the action for use.
  77. *
  78. * Check for a nickname; redirect if non-canonical; if
  79. * not provided, assume public rsd.xml.
  80. *
  81. * @param array $args GET, POST, and URI arguments.
  82. *
  83. * @return boolean success flag
  84. */
  85. function prepare(array $args = array())
  86. {
  87. parent::prepare($args);
  88. // optional argument
  89. $nickname_arg = $this->arg('nickname');
  90. if (empty($nickname_arg)) {
  91. $this->user = null;
  92. } else {
  93. $nickname = common_canonical_nickname($nickname_arg);
  94. // Permanent redirect on non-canonical nickname
  95. if ($nickname_arg != $nickname) {
  96. common_redirect(common_local_url('rsd', array('nickname' => $nickname)), 301);
  97. }
  98. $this->user = User::getKV('nickname', $nickname);
  99. if (empty($this->user)) {
  100. // TRANS: Client error.
  101. $this->clientError(_('No such user.'), 404);
  102. }
  103. }
  104. return true;
  105. }
  106. /**
  107. * Action handler.
  108. *
  109. * Outputs the XML format for an RSD file. May include
  110. * personal information if this is a personal file
  111. * (based on whether $user attribute is set).
  112. *
  113. * @param array $args array of arguments
  114. *
  115. * @return nothing
  116. */
  117. function handle()
  118. {
  119. header('Content-Type: application/rsd+xml');
  120. $this->startXML();
  121. $rsdNS = 'http://archipelago.phrasewise.com/rsd';
  122. $this->elementStart('rsd', array('version' => '1.0',
  123. 'xmlns' => $rsdNS));
  124. $this->elementStart('service');
  125. // TRANS: Engine name for RSD.
  126. $this->element('engineName', null, _('StatusNet'));
  127. $this->element('engineLink', null, 'http://status.net/');
  128. $this->elementStart('apis');
  129. if (Event::handle('StartRsdListApis', array($this, $this->user))) {
  130. $blogID = (empty($this->user)) ? '' : $this->user->nickname;
  131. $apiAttrs = array('name' => 'Twitter',
  132. 'preferred' => 'true',
  133. 'apiLink' => $this->_apiRoot(),
  134. 'blogID' => $blogID);
  135. $this->elementStart('api', $apiAttrs);
  136. $this->elementStart('settings');
  137. $this->element('docs', null,
  138. common_local_url('doc', array('title' => 'api')));
  139. $this->element('setting', array('name' => 'OAuth'),
  140. 'true');
  141. $this->elementEnd('settings');
  142. $this->elementEnd('api');
  143. // Atom API
  144. if (empty($this->user)) {
  145. $service = common_local_url('ApiAtomService');
  146. } else {
  147. $service = common_local_url('ApiAtomService', array('id' => $this->user->nickname));
  148. }
  149. $this->element('api', array('name' => 'Atom',
  150. 'preferred' => 'false',
  151. 'apiLink' => $service,
  152. 'blogID' => $blogID));
  153. Event::handle('EndRsdListApis', array($this, $this->user));
  154. }
  155. $this->elementEnd('apis');
  156. $this->elementEnd('service');
  157. $this->elementEnd('rsd');
  158. $this->endXML();
  159. return true;
  160. }
  161. /**
  162. * Returns last-modified date for use in caching
  163. *
  164. * Per-user rsd.xml is dated to last change of user
  165. * (in case of nickname change); public has no date.
  166. *
  167. * @return string date of last change of this page
  168. */
  169. function lastModified()
  170. {
  171. if (!empty($this->user)) {
  172. return $this->user->modified;
  173. } else {
  174. return null;
  175. }
  176. }
  177. /**
  178. * Flag to indicate if this action is read-only
  179. *
  180. * It is; it doesn't change the DB.
  181. *
  182. * @param array $args ignored
  183. *
  184. * @return boolean true
  185. */
  186. function isReadOnly($args)
  187. {
  188. return true;
  189. }
  190. /**
  191. * Return current site's API root
  192. *
  193. * Varies based on URL parameters, like if fancy URLs are
  194. * turned on.
  195. *
  196. * @return string API root URI for this site
  197. */
  198. private function _apiRoot()
  199. {
  200. if (common_config('site', 'fancy')) {
  201. return common_path('api/', true);
  202. } else {
  203. return common_path('index.php/api/', true);
  204. }
  205. }
  206. }