]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/RSSCloud/RSSCloudRequestNotify.php
Merge branch '0.9.x' into activityexport
[quix0rs-gnu-social.git] / plugins / RSSCloud / RSSCloudRequestNotify.php
1 <?php
2 /**
3  * Action to let RSSCloud aggregators request update notification when
4  * user profile feeds change.
5  *
6  * PHP version 5
7  *
8  * @category Plugin
9  * @package  StatusNet
10  * @author   Zach Copley <zach@status.net>
11  * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
12  * @link     http://status.net/
13  *
14  * StatusNet - the distributed open-source microblogging tool
15  * Copyright (C) 2009, StatusNet, Inc.
16  *
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.
21  *
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.
26  *
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/>.
29  */
30
31 if (!defined('STATUSNET')) {
32     exit(1);
33 }
34
35 /**
36  * Action class to handle RSSCloud notification (subscription) requests
37  *
38  * @category Plugin
39  * @package  StatusNet
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/
43  **/
44 class RSSCloudRequestNotifyAction extends Action
45 {
46     /**
47      * Initialization.
48      *
49      * @param array $args Web and URL arguments
50      *
51      * @return boolean false if user doesn't exist
52      */
53     function prepare($args)
54     {
55         parent::prepare($args);
56
57         $this->ip   = $_SERVER['REMOTE_ADDR'];
58         $this->port = $this->arg('port');
59         $this->path = $this->arg('path');
60
61         if ($this->path[0] != '/') {
62             $this->path = '/' . $this->path;
63         }
64
65         $this->protocol  = $this->arg('protocol');
66         $this->procedure = $this->arg('notifyProcedure');
67         $this->domain    = $this->arg('domain');
68
69         $this->feeds = $this->getFeeds();
70
71         return true;
72     }
73
74     /**
75      * Handle the request
76      *
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.
80      *
81      * @param array $args $_REQUEST data (unused)
82      *
83      * @return void
84      */
85     function handle($args)
86     {
87         parent::handle($args);
88
89         if ($_SERVER['REQUEST_METHOD'] != 'POST') {
90             $this->showResult(false, _m('Request must be POST.'));
91             return;
92         }
93
94         $missing = array();
95
96         if (empty($this->port)) {
97             $missing[] = 'port';
98         }
99
100         if (empty($this->path)) {
101             $missing[] = 'path';
102         }
103
104         if (empty($this->protocol)) {
105             $missing[] = 'protocol';
106         } else if (strtolower($this->protocol) != 'http-post') {
107             $msg = _m('Only http-post notifications are supported at this time.');
108             $this->showResult(false, $msg);
109             return;
110         }
111
112         if (!isset($this->procedure)) {
113             $missing[] = 'notifyProcedure';
114         }
115
116         if (!empty($missing)) {
117             // TRANS: %s is a comma separated list of parameters.
118             $msg = sprintf(_m('The following parameters were missing from the request body: %s.'),implode(', ', $missing));
119             $this->showResult(false, $msg);
120             return;
121         }
122
123         if (empty($this->feeds)) {
124             $msg = _m('You must provide at least one valid profile feed url ' .
125               '(url1, url2, url3 ... urlN).');
126             $this->showResult(false, $msg);
127             return;
128         }
129
130         // We have to validate everything before saving anything.
131         // We only return one success or failure no matter how
132         // many feeds the subscriber is trying to subscribe to
133         foreach ($this->feeds as $feed) {
134
135             if (!$this->validateFeed($feed)) {
136
137                 $nh = $this->getNotifyUrl();
138                 common_log(LOG_WARNING,
139                            "RSSCloud plugin - $nh tried to subscribe to invalid feed: $feed");
140
141                 $msg = _m('Feed subscription failed: Not a valid feed.');
142                 $this->showResult(false, $msg);
143                 return;
144             }
145
146             if (!$this->testNotificationHandler($feed)) {
147                 $msg = _m('Feed subscription failed - ' .
148                 'notification handler doesn\'t respond correctly.');
149                 $this->showResult(false, $msg);
150                 return;
151             }
152         }
153
154         foreach ($this->feeds as $feed) {
155             $this->saveSubscription($feed);
156         }
157
158         // XXX: What to do about deleting stale subscriptions?
159         // 25 hours seems harsh. WordPress doesn't ever remove
160         // subscriptions.
161         $msg = _m('Thanks for the subscription. ' .
162           'When the feed(s) update(s), you will be notified.');
163
164         $this->showResult(true, $msg);
165     }
166
167     /**
168      * Validate that the requested feed is one we serve
169      * up via RSSCloud.
170      *
171      * @param string $feed the feed in question
172      *
173      * @return void
174      */
175     function validateFeed($feed)
176     {
177         $user = $this->userFromFeed($feed);
178
179         if (empty($user)) {
180             return false;
181         }
182
183         return true;
184     }
185
186     /**
187      * Pull all of the urls (url1, url2, url3...urlN) that
188      * the subscriber wants to subscribe to.
189      *
190      * @return array $feeds the list of feeds
191      */
192     function getFeeds()
193     {
194         $feeds = array();
195
196         while (list($key, $feed) = each($this->args)) {
197             if (preg_match('/^url\d*$/', $key)) {
198                 $feeds[] = $feed;
199             }
200         }
201
202         return $feeds;
203     }
204
205     /**
206      * Test that a notification handler is there and is reponding
207      * correctly.  This is called before adding a subscription.
208      *
209      * @param string $feed the feed to verify
210      *
211      * @return boolean success result
212      */
213     function testNotificationHandler($feed)
214     {
215         $notifyUrl = $this->getNotifyUrl();
216
217         $notifier = new RSSCloudNotifier();
218
219         if (isset($this->domain)) {
220             // 'domain' param set, so we have to use GET and send a challenge
221             common_log(LOG_INFO,
222                        'RSSCloud plugin - Testing notification handler with challenge: ' .
223                        $notifyUrl);
224             return $notifier->challenge($notifyUrl, $feed);
225
226         } else {
227             common_log(LOG_INFO, 'RSSCloud plugin - Testing notification handler: ' .
228                        $notifyUrl);
229
230             return $notifier->postUpdate($notifyUrl, $feed);
231         }
232     }
233
234     /**
235      * Build the URL for the notification handler based on the
236      * parameters passed in with the subscription request.
237      *
238      * @return string notification handler url
239      */
240     function getNotifyUrl()
241     {
242         if (isset($this->domain)) {
243             return 'http://' . $this->domain . ':' . $this->port . $this->path;
244         } else {
245             return 'http://' . $this->ip . ':' . $this->port . $this->path;
246         }
247     }
248
249     /**
250      * Uses the nickname part of the subscribed feed URL to figure out
251      * whethere there's really a user with such a feed.  Used to
252      * validate feeds before adding a subscription.
253      *
254      * @param string $feed the feed in question
255      *
256      * @return boolean success
257      */
258     function userFromFeed($feed)
259     {
260         // We only do canonical RSS2 profile feeds (specified by ID), e.g.:
261         // http://www.example.com/api/statuses/user_timeline/2.rss
262         $path  = common_path('api/statuses/user_timeline/');
263         $valid = '%^' . $path . '(?<id>.*)\.rss$%';
264
265         if (preg_match($valid, $feed, $matches)) {
266             $user = User::staticGet('id', $matches['id']);
267             if (!empty($user)) {
268                 return $user;
269             }
270         }
271
272         return false;
273     }
274
275     /**
276      * Save an RSSCloud subscription
277      *
278      * @param string $feed a valid profile feed
279      *
280      * @return boolean success result
281      */
282     function saveSubscription($feed)
283     {
284         $user = $this->userFromFeed($feed);
285
286         $notifyUrl = $this->getNotifyUrl();
287
288         $sub = RSSCloudSubscription::getSubscription($user->id, $notifyUrl);
289
290         if ($sub) {
291             common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl refreshed subscription" .
292                          " to user $user->nickname (id: $user->id).");
293         } else {
294
295             $sub = new RSSCloudSubscription();
296
297             $sub->subscribed = $user->id;
298             $sub->url        = $notifyUrl;
299             $sub->created    = common_sql_now();
300
301             if (!$sub->insert()) {
302                 common_log_db_error($sub, 'INSERT', __FILE__);
303                 return false;
304             }
305
306             common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl subscribed" .
307                        " to user $user->nickname (id: $user->id)");
308         }
309
310         return true;
311     }
312
313     /**
314      * Show an XML message indicating the subscription
315      * was successful or failed.
316      *
317      * @param boolean $success whether it was good or bad
318      * @param string  $msg     the message to output
319      *
320      * @return boolean success result
321      */
322     function showResult($success, $msg)
323     {
324         $this->startXML();
325         $this->elementStart('notifyResult',
326                             array('success' => ($success) ? 'true' : 'false',
327                                   'msg'     => $msg));
328         $this->endXML();
329     }
330 }