apistatusesupdate.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <?php
  2. /**
  3. * StatusNet, the distributed open-source microblogging tool
  4. *
  5. * Post a notice (update your status) through the API
  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 Evan Prodromou <evan@status.net>
  26. * @author Jeffery To <jeffery.to@gmail.com>
  27. * @author Tom Blankenship <mac65@mac65.com>
  28. * @author Mike Cochrane <mikec@mikenz.geek.nz>
  29. * @author Robin Millette <robin@millette.info>
  30. * @author Zach Copley <zach@status.net>
  31. * @copyright 2009-2010 StatusNet, Inc.
  32. * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
  33. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  34. * @link http://status.net/
  35. */
  36. /* External API usage documentation. Please update when you change how this method works. */
  37. /*! @page statusesupdate statuses/update
  38. @section Description
  39. Updates the authenticating user's status. Requires the status parameter specified below.
  40. Request must be a POST.
  41. @par URL pattern
  42. /api/statuses/update.:format
  43. @par Formats (:format)
  44. xml, json
  45. @par HTTP Method(s)
  46. POST
  47. @par Requires Authentication
  48. Yes
  49. @param status (Required) The URL-encoded text of the status update.
  50. @param source (Optional) The source application name, if using HTTP authentication or an anonymous OAuth consumer.
  51. @param in_reply_to_status_id (Optional) The ID of an existing status that the update is in reply to.
  52. @param lat (Optional) The latitude the status refers to.
  53. @param long (Optional) The longitude the status refers to.
  54. @param media (Optional) a media upload, such as an image or movie file.
  55. @sa @ref authentication
  56. @sa @ref apiroot
  57. @subsection usagenotes Usage notes
  58. @li The URL pattern is relative to the @ref apiroot.
  59. @li If the @e source parameter is not supplied the source of the status will default to 'api'. When authenticated via a registered OAuth application, the application's registered name and URL will always override the source parameter.
  60. @li The XML response uses <a href="http://georss.org/Main_Page">GeoRSS</a>
  61. to encode the latitude and longitude (see example response below <georss:point>).
  62. @li Data uploaded via the @e media parameter should be multipart/form-data encoded.
  63. @subsection exampleusage Example usage
  64. @verbatim
  65. curl -u username:password http://example.com/api/statuses/update.xml -d status='Howdy!' -d lat='30.468' -d long='-94.743'
  66. @endverbatim
  67. @subsection exampleresponse Example response
  68. @verbatim
  69. <?xml version="1.0" encoding="UTF-8"?>
  70. <status>
  71. <text>Howdy!</text>
  72. <truncated>false</truncated>
  73. <created_at>Tue Mar 30 23:28:05 +0000 2010</created_at>
  74. <in_reply_to_status_id/>
  75. <source>api</source>
  76. <id>26668724</id>
  77. <in_reply_to_user_id/>
  78. <in_reply_to_screen_name/>
  79. <geo xmlns:georss="http://www.georss.org/georss">
  80. <georss:point>30.468 -94.743</georss:point>
  81. </geo>
  82. <favorited>false</favorited>
  83. <user>
  84. <id>25803</id>
  85. <name>Jed Sanders</name>
  86. <screen_name>jedsanders</screen_name>
  87. <location>Hoop and Holler, Texas</location>
  88. <description>I like to think of myself as America's Favorite.</description>
  89. <profile_image_url>http://avatar.example.com/25803-48-20080924200604.png</profile_image_url>
  90. <url>http://jedsanders.net</url>
  91. <protected>false</protected>
  92. <followers_count>5</followers_count>
  93. <profile_background_color/>
  94. <profile_text_color/>
  95. <profile_link_color/>
  96. <profile_sidebar_fill_color/>
  97. <profile_sidebar_border_color/>
  98. <friends_count>2</friends_count>
  99. <created_at>Wed Sep 24 20:04:00 +0000 2008</created_at>
  100. <favourites_count>0</favourites_count>
  101. <utc_offset>0</utc_offset>
  102. <time_zone>UTC</time_zone>
  103. <profile_background_image_url/>
  104. <profile_background_tile>false</profile_background_tile>
  105. <statuses_count>70</statuses_count>
  106. <following>true</following>
  107. <notifications>true</notifications>
  108. </user>
  109. </status>
  110. @endverbatim
  111. */
  112. if (!defined('STATUSNET')) {
  113. exit(1);
  114. }
  115. /**
  116. * Updates the authenticating user's status (posts a notice).
  117. *
  118. * @category API
  119. * @package StatusNet
  120. * @author Craig Andrews <candrews@integralblue.com>
  121. * @author Evan Prodromou <evan@status.net>
  122. * @author Jeffery To <jeffery.to@gmail.com>
  123. * @author Tom Blankenship <mac65@mac65.com>
  124. * @author Mike Cochrane <mikec@mikenz.geek.nz>
  125. * @author Robin Millette <robin@millette.info>
  126. * @author Zach Copley <zach@status.net>
  127. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  128. * @link http://status.net/
  129. */
  130. class ApiStatusesUpdateAction extends ApiAuthAction
  131. {
  132. protected $needPost = true;
  133. var $status = null;
  134. var $in_reply_to_status_id = null;
  135. var $lat = null;
  136. var $lon = null;
  137. /**
  138. * Take arguments for running
  139. *
  140. * @param array $args $_REQUEST args
  141. *
  142. * @return boolean success flag
  143. */
  144. protected function prepare(array $args=array())
  145. {
  146. parent::prepare($args);
  147. $this->status = $this->trimmed('status');
  148. $this->lat = $this->trimmed('lat');
  149. $this->lon = $this->trimmed('long');
  150. $this->in_reply_to_status_id
  151. = intval($this->trimmed('in_reply_to_status_id'));
  152. return true;
  153. }
  154. /**
  155. * Handle the request
  156. *
  157. * Make a new notice for the update, save it, and show it
  158. *
  159. * @return void
  160. */
  161. protected function handle()
  162. {
  163. parent::handle();
  164. // Workaround for PHP returning empty $_POST and $_FILES when POST
  165. // length > post_max_size in php.ini
  166. if (empty($_FILES)
  167. && empty($_POST)
  168. && ($_SERVER['CONTENT_LENGTH'] > 0)
  169. ) {
  170. // TRANS: Client error displayed when the number of bytes in a POST request exceeds a limit.
  171. // TRANS: %s is the number of bytes of the CONTENT_LENGTH.
  172. $msg = _m('The server was unable to handle that much POST data (%s byte) due to its current configuration.',
  173. 'The server was unable to handle that much POST data (%s bytes) due to its current configuration.',
  174. intval($_SERVER['CONTENT_LENGTH']));
  175. $this->clientError(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
  176. }
  177. if (empty($this->status)) {
  178. // TRANS: Client error displayed when the parameter "status" is missing.
  179. $this->clientError(_('Client must provide a \'status\' parameter with a value.'));
  180. }
  181. if (is_null($this->scoped)) {
  182. // TRANS: Client error displayed when updating a status for a non-existing user.
  183. $this->clientError(_('No such user.'), 404);
  184. }
  185. /* Do not call shortenlinks until the whole notice has been build */
  186. // Check for commands
  187. $inter = new CommandInterpreter();
  188. $cmd = $inter->handle_command($this->auth_user, $this->status);
  189. if ($cmd) {
  190. if ($this->supported($cmd)) {
  191. $cmd->execute(new Channel());
  192. }
  193. // Cmd not supported? Twitter just returns your latest status.
  194. // And, it returns your last status whether the cmd was successful
  195. // or not!
  196. $this->notice = $this->auth_user->getCurrentNotice();
  197. } else {
  198. $reply_to = null;
  199. if (!empty($this->in_reply_to_status_id)) {
  200. // Check whether notice actually exists
  201. $reply = Notice::getKV($this->in_reply_to_status_id);
  202. if ($reply) {
  203. $reply_to = $this->in_reply_to_status_id;
  204. } else {
  205. // TRANS: Client error displayed when replying to a non-existing notice.
  206. $this->clientError(_('Parent notice not found.'), 404);
  207. }
  208. }
  209. $upload = null;
  210. try {
  211. $upload = MediaFile::fromUpload('media', $this->scoped);
  212. $this->status .= ' ' . $upload->shortUrl();
  213. /* Do not call shortenlinks until the whole notice has been build */
  214. } catch (NoUploadedMediaException $e) {
  215. // There was no uploaded media for us today.
  216. }
  217. /* Do call shortenlinks here & check notice length since notice is about to be saved & sent */
  218. $status_shortened = $this->auth_user->shortenlinks($this->status);
  219. if (Notice::contentTooLong($status_shortened)) {
  220. if ($upload instanceof MediaFile) {
  221. $upload->delete();
  222. }
  223. // TRANS: Client error displayed exceeding the maximum notice length.
  224. // TRANS: %d is the maximum lenth for a notice.
  225. $msg = _m('Maximum notice size is %d character, including attachment URL.',
  226. 'Maximum notice size is %d characters, including attachment URL.',
  227. Notice::maxContent());
  228. /* Use HTTP 413 error code (Request Entity Too Large)
  229. * instead of basic 400 for better understanding
  230. */
  231. $this->clientError(sprintf($msg, Notice::maxContent()), 413);
  232. }
  233. $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8');
  234. $options = array('reply_to' => $reply_to);
  235. if ($this->scoped->shareLocation()) {
  236. $locOptions = Notice::locationOptions($this->lat,
  237. $this->lon,
  238. null,
  239. null,
  240. $this->scoped);
  241. $options = array_merge($options, $locOptions);
  242. }
  243. try {
  244. $this->notice = Notice::saveNew(
  245. $this->scoped->id,
  246. $content,
  247. $this->source,
  248. $options
  249. );
  250. } catch (Exception $e) {
  251. $this->clientError($e->getMessage(), $e->getCode());
  252. }
  253. if (isset($upload)) {
  254. $upload->attachToNotice($this->notice);
  255. }
  256. }
  257. $this->showNotice();
  258. }
  259. /**
  260. * Show the resulting notice
  261. *
  262. * @return void
  263. */
  264. function showNotice()
  265. {
  266. if (!empty($this->notice)) {
  267. if ($this->format == 'xml') {
  268. $this->showSingleXmlStatus($this->notice);
  269. } elseif ($this->format == 'json') {
  270. $this->show_single_json_status($this->notice);
  271. }
  272. }
  273. }
  274. /**
  275. * Is this command supported when doing an update from the API?
  276. *
  277. * @param string $cmd the command to check for
  278. *
  279. * @return boolean true or false
  280. */
  281. function supported($cmd)
  282. {
  283. static $cmdlist = array('SubCommand', 'UnsubCommand',
  284. 'OnCommand', 'OffCommand', 'JoinCommand', 'LeaveCommand');
  285. $supported = null;
  286. if (Event::handle('CommandSupportedAPI', array($cmd, &$supported))) {
  287. $supported = $supported || in_array(get_class($cmd), $cmdlist);
  288. }
  289. return $supported;
  290. }
  291. }