3 * Action to let RSSCloud aggregators request update notification when
4 * user profile feeds change.
10 * @author Zach Copley <zach@status.net>
11 * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
12 * @link http://status.net/
14 * StatusNet - the distributed open-source microblogging tool
15 * Copyright (C) 2009, StatusNet, Inc.
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU Affero General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU Affero General Public License for more details.
27 * You should have received a copy of the GNU Affero General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 if (!defined('STATUSNET')) {
36 * Action class to handle RSSCloud notification (subscription) requests
40 * @author Zach Copley <zach@status.net>
41 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
42 * @link http://status.net/
44 class RSSCloudRequestNotifyAction extends Action
49 * @param array $args Web and URL arguments
51 * @return boolean false if user doesn't exist
53 function prepare($args)
55 parent::prepare($args);
57 $this->ip = $_SERVER['REMOTE_ADDR'];
58 $this->port = $this->arg('port');
59 $this->path = $this->arg('path');
61 if ($this->path[0] != '/') {
62 $this->path = '/' . $this->path;
65 $this->protocol = $this->arg('protocol');
66 $this->procedure = $this->arg('notifyProcedure');
67 $this->domain = $this->arg('domain');
69 $this->feeds = $this->getFeeds();
77 * Checks for all the required parameters for a subscription,
78 * validates that the feed being subscribed to is real, and then
79 * saves the subsctiption.
81 * @param array $args $_REQUEST data (unused)
85 function handle($args)
87 parent::handle($args);
89 if ($_SERVER['REQUEST_METHOD'] != 'POST') {
90 // TRANS: Form validation error displayed when POST is not used.
91 $this->showResult(false, _m('Request must be POST.'));
97 if (empty($this->port)) {
101 if (empty($this->path)) {
105 if (empty($this->protocol)) {
106 $missing[] = 'protocol';
107 } else if (strtolower($this->protocol) != 'http-post') {
108 // TRANS: Form validation error displayed when HTTP POST is not used.
109 $msg = _m('Only HTTP POST notifications are supported at this time.');
110 $this->showResult(false, $msg);
114 if (!isset($this->procedure)) {
115 $missing[] = 'notifyProcedure';
118 if (!empty($missing)) {
119 // TRANS: List separator.
120 $separator = _m('SEPARATOR',', ');
121 // TRANS: Form validation error displayed when a request body is missing expected parameters.
122 // TRANS: %s is a list of parameters separated by a list separator (default: ", ").
123 $msg = sprintf(_m('The following parameters were missing from the request body: %s.'),implode($separator, $missing));
124 $this->showResult(false, $msg);
128 if (empty($this->feeds)) {
129 // TRANS: Form validation error displayed when not providing any valid profile feed URLs.
130 $msg = _m('You must provide at least one valid profile feed URL ' .
131 '(url1, url2, url3 ... urlN).');
132 $this->showResult(false, $msg);
136 // We have to validate everything before saving anything.
137 // We only return one success or failure no matter how
138 // many feeds the subscriber is trying to subscribe to
139 foreach ($this->feeds as $feed) {
140 if (!$this->validateFeed($feed)) {
141 $nh = $this->getNotifyUrl();
142 common_log(LOG_WARNING,
143 "RSSCloud plugin - $nh tried to subscribe to invalid feed: $feed");
145 // TRANS: Form validation error displayed when not providing a valid feed URL.
146 $msg = _m('Feed subscription failed: Not a valid feed.');
147 $this->showResult(false, $msg);
151 if (!$this->testNotificationHandler($feed)) {
152 // TRANS: Form validation error displayed when feed subscription failed.
153 $msg = _m('Feed subscription failed: ' .
154 'Notification handler does not respond correctly.');
155 $this->showResult(false, $msg);
160 foreach ($this->feeds as $feed) {
161 $this->saveSubscription($feed);
164 // XXX: What to do about deleting stale subscriptions?
165 // 25 hours seems harsh. WordPress doesn't ever remove
167 // TRANS: Success message after subscribing to one or more feeds.
168 $msg = _m('Thanks for the subscription. ' .
169 'When the feed(s) update(s), you will be notified.');
171 $this->showResult(true, $msg);
175 * Validate that the requested feed is one we serve
178 * @param string $feed the feed in question
182 function validateFeed($feed)
184 $user = $this->userFromFeed($feed);
194 * Pull all of the urls (url1, url2, url3...urlN) that
195 * the subscriber wants to subscribe to.
197 * @return array $feeds the list of feeds
203 while (list($key, $feed) = each($this->args)) {
204 if (preg_match('/^url\d*$/', $key)) {
213 * Test that a notification handler is there and is reponding
214 * correctly. This is called before adding a subscription.
216 * @param string $feed the feed to verify
218 * @return boolean success result
220 function testNotificationHandler($feed)
222 $notifyUrl = $this->getNotifyUrl();
224 $notifier = new RSSCloudNotifier();
226 if (isset($this->domain)) {
227 // 'domain' param set, so we have to use GET and send a challenge
229 'RSSCloud plugin - Testing notification handler with challenge: ' .
231 return $notifier->challenge($notifyUrl, $feed);
233 common_log(LOG_INFO, 'RSSCloud plugin - Testing notification handler: ' .
236 return $notifier->postUpdate($notifyUrl, $feed);
241 * Build the URL for the notification handler based on the
242 * parameters passed in with the subscription request.
244 * @return string notification handler url
246 function getNotifyUrl()
248 if (isset($this->domain)) {
249 return 'http://' . $this->domain . ':' . $this->port . $this->path;
251 return 'http://' . $this->ip . ':' . $this->port . $this->path;
256 * Uses the nickname part of the subscribed feed URL to figure out
257 * whethere there's really a user with such a feed. Used to
258 * validate feeds before adding a subscription.
260 * @param string $feed the feed in question
262 * @return boolean success
264 function userFromFeed($feed)
266 // We only do canonical RSS2 profile feeds (specified by ID), e.g.:
267 // http://www.example.com/api/statuses/user_timeline/2.rss
268 $path = common_path('api/statuses/user_timeline/');
269 $valid = '%^' . $path . '(?<id>.*)\.rss$%';
271 if (preg_match($valid, $feed, $matches)) {
272 $user = User::getKV('id', $matches['id']);
282 * Save an RSSCloud subscription
284 * @param string $feed a valid profile feed
286 * @return boolean success result
288 function saveSubscription($feed)
290 $user = $this->userFromFeed($feed);
292 $notifyUrl = $this->getNotifyUrl();
294 $sub = RSSCloudSubscription::getSubscription($user->id, $notifyUrl);
297 common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl refreshed subscription" .
298 " to user $user->nickname (id: $user->id).");
300 $sub = new RSSCloudSubscription();
302 $sub->subscribed = $user->id;
303 $sub->url = $notifyUrl;
304 $sub->created = common_sql_now();
306 if (!$sub->insert()) {
307 common_log_db_error($sub, 'INSERT', __FILE__);
311 common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl subscribed" .
312 " to user $user->nickname (id: $user->id)");
319 * Show an XML message indicating the subscription
320 * was successful or failed.
322 * @param boolean $success whether it was good or bad
323 * @param string $msg the message to output
325 * @return boolean success result
327 function showResult($success, $msg)
330 $this->elementStart('notifyResult',
331 array('success' => ($success) ? 'true' : 'false',