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/
45 class RSSCloudRequestNotifyAction extends Action
50 * @param array $args Web and URL arguments
52 * @return boolean false if user doesn't exist
55 function prepare($args)
57 parent::prepare($args);
59 $this->ip = $_SERVER['REMOTE_ADDR'];
60 $this->port = $this->arg('port');
61 $this->path = $this->arg('path');
63 if ($this->path[0] != '/') {
64 $this->path = '/' . $this->path;
67 $this->protocol = $this->arg('protocol');
68 $this->procedure = $this->arg('notifyProcedure');
69 $this->domain = $this->arg('domain');
71 $this->feeds = $this->getFeeds();
79 * Checks for all the required parameters for a subscription,
80 * validates that the feed being subscribed to is real, and then
81 * saves the subsctiption.
83 * @param array $args $_REQUEST data (unused)
88 function handle($args)
90 parent::handle($args);
92 if ($_SERVER['REQUEST_METHOD'] != 'POST') {
93 $this->showResult(false, 'Request must be POST.');
99 if (empty($this->port)) {
103 if (empty($this->path)) {
107 if (empty($this->protocol)) {
108 $missing[] = 'protocol';
111 if (!isset($this->procedure)) {
112 $missing[] = 'notifyProcedure';
115 if (!empty($missing)) {
116 $msg = 'The following parameters were missing from the request body: ' .
117 implode(', ', $missing) . '.';
118 $this->showResult(false, $msg);
122 if (empty($this->feeds)) {
123 $this->showResult(false,
124 'You must provide at least one valid profile feed url (url1, url2, url3 ... urlN).');
128 // We have to validate everything before saving anything.
129 // We only return one success or failure no matter how
130 // many feeds the subscriber is trying to subscribe to
132 foreach ($this->feeds as $feed) {
134 if (!$this->validateFeed($feed)) {
135 $msg = 'Feed subscription failed - Not a valid feed.';
136 $this->showResult(false, $msg);
140 if (!$this->testNotificationHandler($feed)) {
141 $msg = 'Feed subscription failed - ' .
142 'notification handler doesn\'t respond correctly.';
143 $this->showResult(false, $msg);
149 foreach ($this->feeds as $feed) {
150 $this->saveSubscription($feed);
153 // XXX: What to do about deleting stale subscriptions?
154 // 25 hours seems harsh. WordPress doesn't ever remove
157 $msg = 'Thanks for the subscription. ' .
158 'When the feed(s) update(s) we\'ll notify you.';
160 $this->showResult(true, $msg);
164 * Validate that the requested feed is one we serve
167 * @param string $feed the feed in question
172 function validateFeed($feed)
174 $user = $this->userFromFeed($feed);
184 * Pull all of the urls (url1, url2, url3...urlN) that
185 * the subscriber wants to subscribe to.
187 * @return array $feeds the list of feeds
194 while (list($key, $feed) = each ($this->args)) {
195 if (preg_match('/^url\d*$/', $key)) {
204 * Test that a notification handler is there and is reponding
205 * correctly. This is called before adding a subscription.
207 * @param string $feed the feed to verify
209 * @return boolean success result
212 function testNotificationHandler($feed)
214 common_debug("RSSCloudPlugin - testNotificationHandler()");
216 $notifyUrl = $this->getNotifyUrl();
218 $notifier = new RSSCloudNotifier();
220 if (isset($this->domain)) {
222 // 'domain' param set, so we have to use GET and send a challenge
224 common_log(LOG_INFO, 'Testing notification handler with challenge: ' .
226 return $notifier->challenge($notifyUrl, $feed);
229 common_log(LOG_INFO, 'Testing notification handler: ' .
232 return $notifier->postUpdate($notifyUrl, $feed);
237 * Build the URL for the notification handler based on the
238 * parameters passed in with the subscription request.
240 * @return string notification handler url
243 function getNotifyUrl()
245 if (isset($this->domain)) {
246 return 'http://' . $this->domain . ':' . $this->port . $this->path;
248 return 'http://' . $this->ip . ':' . $this->port . $this->path;
253 * Uses the nickname part of the subscribed feed URL to figure out
254 * whethere there's really a user with such a feed. Used to
255 * validate feeds before adding a subscription.
257 * @param string $feed the feed in question
259 * @return boolean success
262 function userFromFeed($feed)
264 // We only do profile feeds
266 $path = common_path('api/statuses/user_timeline/');
267 $valid = '%^' . $path . '(?<nickname>.*)\.rss$%';
269 if (preg_match($valid, $feed, $matches)) {
270 $user = User::staticGet('nickname', $matches['nickname']);
280 * Save an RSSCloud subscription
282 * @param $feed a valid profile feed
284 * @return boolean success result
287 function saveSubscription($feed)
289 $user = $this->userFromFeed($feed);
291 $notifyUrl = $this->getNotifyUrl();
293 $sub = RSSCloudSubscription::getSubscription($user->id, $notifyUrl);
296 common_debug("Already subscribed to that!");
299 $sub = new RSSCloudSubscription();
301 $sub->subscribed = $user->id;
302 $sub->url = $notifyUrl;
303 $sub->created = common_sql_now();
305 if (!$sub->insert()) {
306 common_log_db_error($sub, 'INSERT', __FILE__);
316 * Show an XML message indicating the subscription
317 * was successful or failed.
319 * @param boolean $success whether it was good or bad
320 * @param string $msg the message to output
322 * @return boolean success result
325 function showResult($success, $msg)
328 $this->elementStart('notifyResult', array('success' => ($success) ? 'true' : 'false',