rsscloudnotifier.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Class to ping an rssCloud endpoint when a feed has been updated
  18. *
  19. * @category Plugin
  20. * @package GNUsocial
  21. * @author Zach Copley <zach@status.net>
  22. * @copyright 2009 StatusNet, Inc.
  23. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  24. */
  25. defined('STATUSNET') || die();
  26. /**
  27. * Class for notifying cloud-enabled RSS aggregators that StatusNet
  28. * feeds have been updated.
  29. *
  30. * @category Plugin
  31. * @package GNUsocial
  32. * @author Zach Copley <zach@status.net>
  33. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  34. */
  35. class RSSCloudNotifier
  36. {
  37. const MAX_FAILURES = 3;
  38. /**
  39. * Send an HTTP GET to the notification handler with a
  40. * challenge string to see if it repsonds correctly.
  41. *
  42. * @param string $endpoint URL of the notification handler
  43. * @param string $feed the feed being subscribed to
  44. *
  45. * @return boolean success
  46. */
  47. public function challenge($endpoint, $feed)
  48. {
  49. $code = common_confirmation_code(128);
  50. $params = array('url' => $feed, 'challenge' => $code);
  51. $url = $endpoint . '?' . http_build_query($params);
  52. try {
  53. $client = new HTTPClient();
  54. $response = $client->get($url);
  55. } catch (Exception $e) {
  56. common_log(
  57. LOG_INFO,
  58. 'RSSCloud plugin - failure testing notify handler '
  59. . $endpoint . ' - ' . $e->getMessage()
  60. );
  61. return false;
  62. }
  63. // Check response is betweet 200 and 299 and body contains challenge data
  64. $status = $response->getStatus();
  65. $body = $response->getBody();
  66. if ($status >= 200 && $status < 300) {
  67. // NOTE: the spec says that the body must contain the string
  68. // challenge. It doesn't say that the body must contain the
  69. // challenge string ONLY, although that seems to be the way
  70. // the other implementors have interpreted it.
  71. if (strpos($body, $code) !== false) {
  72. common_log(LOG_INFO, 'RSSCloud plugin - ' .
  73. "success testing notify handler: $endpoint");
  74. return true;
  75. } else {
  76. common_log(LOG_INFO, 'RSSCloud plugin - ' .
  77. 'challenge/repsonse failed for notify handler ' .
  78. $endpoint);
  79. common_debug('body = ' . var_export($body, true));
  80. return false;
  81. }
  82. } else {
  83. common_log(LOG_INFO, 'RSSCloud plugin - ' .
  84. "failure testing notify handler: $endpoint " .
  85. ' - got HTTP ' . $status);
  86. common_debug('body = ' . var_export($body, true));
  87. return false;
  88. }
  89. }
  90. /**
  91. * HTTP POST a notification that a feed has been updated
  92. * ('ping the cloud').
  93. *
  94. * @param String $endpoint URL of the notification handler
  95. * @param String $feed the feed being subscribed to
  96. *
  97. * @return boolean success
  98. */
  99. public function postUpdate($endpoint, $feed)
  100. {
  101. $headers = array();
  102. $postdata = array('url' => $feed);
  103. try {
  104. $client = new HTTPClient();
  105. $response = $client->post($endpoint, $headers, $postdata);
  106. } catch (Exception $e) {
  107. common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
  108. $endpoint . ' that feed ' . $feed .
  109. ' has changed: ' . $e->getMessage());
  110. return false;
  111. }
  112. $status = $response->getStatus();
  113. if ($status >= 200 && $status < 300) {
  114. common_log(LOG_INFO, 'RSSCloud plugin - success notifying ' .
  115. $endpoint . ' that feed ' . $feed . ' has changed.');
  116. return true;
  117. } else {
  118. common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
  119. $endpoint . ' that feed ' . $feed .
  120. ' has changed: got HTTP ' . $status);
  121. return false;
  122. }
  123. }
  124. /**
  125. * Notify all subscribers to a profile feed that it has changed.
  126. *
  127. * @param Profile $profile the profile whose feed has been
  128. * updated
  129. *
  130. * @return boolean success
  131. */
  132. public function notify($profile)
  133. {
  134. $feed = common_path('api/statuses/user_timeline/') .
  135. $profile->id . '.rss';
  136. $cloudSub = new RSSCloudSubscription();
  137. $cloudSub->subscribed = $profile->id;
  138. if ($cloudSub->find()) {
  139. while ($cloudSub->fetch()) {
  140. $result = $this->postUpdate($cloudSub->url, $feed);
  141. if ($result == false) {
  142. $this->handleFailure($cloudSub);
  143. }
  144. }
  145. }
  146. return true;
  147. }
  148. /**
  149. * Handle problems posting cloud notifications. Increment the failure
  150. * count, or delete the subscription if the maximum number of failures
  151. * is exceeded.
  152. *
  153. * XXX: Redo with proper DB_DataObject methods once I figure out what
  154. * what the problem is with pluginized DB_DataObjects. -Z
  155. *
  156. * @param RSSCloudSubscription $cloudSub the subscription in question
  157. *
  158. * @return boolean success
  159. */
  160. public function handleFailure($cloudSub)
  161. {
  162. $failCnt = $cloudSub->failures + 1;
  163. if ($failCnt == self::MAX_FAILURES) {
  164. common_log(
  165. LOG_INFO,
  166. 'Deleting RSSCloud subcription (max failure count reached), '
  167. . "profile: {$cloudSub->subscribed} handler: {$cloudSub->url}"
  168. );
  169. // XXX: WTF! ->delete() doesn't work. Clearly, there are some issues with
  170. // the DB_DataObject, or my understanding of it. Have to drop into SQL.
  171. // $result = $cloudSub->delete();
  172. $qry = 'DELETE from rsscloud_subscription' .
  173. ' WHERE subscribed = ' . $cloudSub->subscribed .
  174. ' AND url = \'' . $cloudSub->url . '\'';
  175. $result = $cloudSub->query($qry);
  176. if (!$result) {
  177. common_log_db_error($cloudSub, 'DELETE', __FILE__);
  178. common_log(LOG_ERR, 'Could not delete RSSCloud subscription.');
  179. }
  180. } else {
  181. common_debug('Updating failure count on RSSCloud subscription. ' .
  182. $failCnt);
  183. $failCnt = $cloudSub->failures + 1;
  184. // XXX: ->update() not working either, gar!
  185. $result = $cloudSub->query(sprintf(
  186. <<<'END'
  187. UPDATE rsscloud_subscription
  188. SET failures = %1$d, modified = CURRENT_TIMESTAMP
  189. WHERE subscribed = %2$d AND url = %3$s
  190. END,
  191. $failCnt,
  192. $cloudSub->subscribed,
  193. $cloudSub->_quote($cloudSub->url)
  194. ));
  195. if (!$result) {
  196. common_log_db_error($cloudsub, 'UPDATE', __FILE__);
  197. common_log(
  198. LOG_ERR,
  199. 'Could not update failure count on RSSCloud subscription'
  200. );
  201. }
  202. }
  203. }
  204. }