]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/FeedSub/feedinfo.php
Merge branch 'inblob' of git@gitorious.org:~evan/statusnet/evans-mainline into inblob
[quix0rs-gnu-social.git] / plugins / FeedSub / feedinfo.php
1 <?php
2
3 /*
4
5 Subscription flow:
6
7     $feedinfo->subscribe()
8         generate random verification token
9             save to verify_token
10         sends a sub request to the hub...
11     
12     feedsub/callback
13         hub sends confirmation back to us via GET
14         We verify the request, then echo back the challenge.
15         On our end, we save the time we subscribed and the lease expiration
16     
17     feedsub/callback
18         hub sends us updates via POST
19         ?
20     
21 */
22
23 class FeedDBException extends FeedSubException
24 {
25     public $obj;
26
27     function __construct($obj)
28     {
29         parent::__construct('Database insert failure');
30         $this->obj = $obj;
31     }
32 }
33
34 class Feedinfo extends Memcached_DataObject
35 {
36     public $__table = 'feedinfo';
37
38     public $id;
39     public $profile_id;
40
41     public $feeduri;
42     public $homeuri;
43     public $huburi;
44
45     // PuSH subscription data
46     public $verify_token;
47     public $sub_start;
48     public $sub_end;
49
50     public $created;
51     public $lastupdate;
52
53
54     public /*static*/ function staticGet($k, $v=null)
55     {
56         return parent::staticGet(__CLASS__, $k, $v);
57     }
58
59     /**
60      * return table definition for DB_DataObject
61      *
62      * DB_DataObject needs to know something about the table to manipulate
63      * instances. This method provides all the DB_DataObject needs to know.
64      *
65      * @return array array of column definitions
66      */
67
68     function table()
69     {
70         return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
71                      'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
72                      'feeduri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
73                      'homeuri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
74                      'huburi' =>  DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
75                      'verify_token' => DB_DATAOBJECT_STR,
76                      'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
77                      'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
78                      'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
79                      'lastupdate' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
80     }
81     
82     static function schemaDef()
83     {
84         return array(new ColumnDef('id', 'integer',
85                                    /*size*/ null,
86                                    /*nullable*/ false,
87                                    /*key*/ 'PRI',
88                                    /*default*/ '0',
89                                    /*extra*/ null,
90                                    /*auto_increment*/ true),
91                      new ColumnDef('profile_id', 'integer',
92                                    null, false),
93                      new ColumnDef('feeduri', 'varchar',
94                                    255, false, 'UNI'),
95                      new ColumnDef('homeuri', 'varchar',
96                                    255, false),
97                      new ColumnDef('huburi', 'varchar',
98                                    255, false),
99                      new ColumnDef('verify_token', 'varchar',
100                                    32, true),
101                      new ColumnDef('sub_start', 'datetime',
102                                    null, true),
103                      new ColumnDef('sub_end', 'datetime',
104                                    null, true),
105                      new ColumnDef('created', 'datetime',
106                                    null, false),
107                      new ColumnDef('lastupdate', 'datetime',
108                                    null, false));
109     }
110
111     /**
112      * return key definitions for DB_DataObject
113      *
114      * DB_DataObject needs to know about keys that the table has; this function
115      * defines them.
116      *
117      * @return array key definitions
118      */
119
120     function keys()
121     {
122         return array('id' => 'P'); //?
123     }
124
125     /**
126      * return key definitions for Memcached_DataObject
127      *
128      * Our caching system uses the same key definitions, but uses a different
129      * method to get them.
130      *
131      * @return array key definitions
132      */
133
134     function keyTypes()
135     {
136         return $this->keys();
137     }
138
139     /**
140      * Fetch the StatusNet-side profile for this feed
141      * @return Profile
142      */
143     public function getProfile()
144     {
145         return Profile::staticGet('id', $this->profile_id);
146     }
147
148     /**
149      * @param FeedMunger $munger
150      * @return Feedinfo
151      */
152     public static function ensureProfile($munger)
153     {
154         $feedinfo = $munger->feedinfo();
155
156         $current = self::staticGet('feeduri', $feedinfo->feeduri);
157         if ($current) {
158             // @fixme we should probably update info as necessary
159             return $current;
160         }
161
162         $feedinfo->query('BEGIN');
163
164         try {
165             $profile = $munger->profile();
166             $result = $profile->insert();
167             if (empty($result)) {
168                 throw new FeedDBException($profile);
169             }
170
171             $feedinfo->profile_id = $profile->id;
172             $result = $feedinfo->insert();
173             if (empty($result)) {
174                 throw new FeedDBException($feedinfo);
175             }
176
177             $feedinfo->query('COMMIT');
178         } catch (FeedDBException $e) {
179             common_log_db_error($e->obj, 'INSERT', __FILE__);
180             $feedinfo->query('ROLLBACK');
181             return false;
182         }
183         return $feedinfo;
184     }
185
186     /**
187      * Send a subscription request to the hub for this feed.
188      * The hub will later send us a confirmation POST to /feedsub/callback.
189      *
190      * @return bool true on success, false on failure
191      */
192     public function subscribe()
193     {
194         // @fixme use the verification token
195         #$token = md5(mt_rand() . ':' . $this->feeduri);
196         #$this->verify_token = $token;
197         #$this->update(); // @fixme
198         
199         try {
200             $callback = common_local_url('feedsubcallback', array('feed' => $this->id));
201             $headers = array('Content-Type: application/x-www-form-urlencoded');
202             $post = array('hub.mode' => 'subscribe',
203                           'hub.callback' => $callback,
204                           'hub.verify' => 'async',
205                           //'hub.verify_token' => $token,
206                           //'hub.lease_seconds' => 0,
207                           'hub.topic' => $this->feeduri);
208             $client = new HTTPClient();
209             $response = $client->post($this->huburi, $headers, $post);
210             if ($response->getStatus() >= 200 && $response->getStatus() < 300) {
211                 common_log(LOG_INFO, __METHOD__ . ': sub req ok');
212                 return true;
213             } else {
214                 common_log(LOG_INFO, __METHOD__ . ': sub req failed');
215                 return false;
216             }
217         } catch (Exception $e) {
218             // wtf!
219             common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub $this->huburi subscribing to $this->feeduri");
220             return false;
221         }
222     }
223
224     /**
225      * Read and post notices for updates from the feed.
226      * Currently assumes that all items in the feed are new,
227      * coming from a PuSH hub.
228      *
229      * @param string $xml source of Atom or RSS feed
230      */
231     public function postUpdates($xml)
232     {
233         common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $xml");
234         require_once "XML/Feed/Parser.php";
235         $feed = new XML_Feed_Parser($xml, false, false, true);
236         $munger = new FeedMunger($feed);
237         
238         $hits = 0;
239         foreach ($feed as $index => $entry) {
240             // @fixme this might sort in wrong order if we get multiple updates
241             
242             $notice = $munger->notice($index);
243             $notice->profile_id = $this->profile_id;
244             
245             // Double-check for oldies
246             // @fixme this could explode horribly for multiple feeds on a blog. sigh
247             $dupe = new Notice();
248             $dupe->uri = $notice->uri;
249             $dupe->find();
250             if ($dupe->fetch()) {
251                 common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}");
252                 continue;
253             }
254             
255             if (Event::handle('StartNoticeSave', array(&$notice))) {
256                 $id = $notice->insert();
257                 Event::handle('EndNoticeSave', array($notice));
258             }
259             $notice->addToInboxes();
260
261             common_log(LOG_INFO, __METHOD__ . ": saved notice {$notice->id} for entry $index of update to \"{$this->feeduri}\"");
262             $hits++;
263         }
264         if ($hits == 0) {
265             common_log(LOG_INFO, __METHOD__ . ": no updates in packet for \"$this->feeduri\"! $xml");
266         }
267     }
268 }