454d191266e114d813718d105e69504169ca8dd7
[phinde.git] / src / phinde / Subscriptions.php
1 <?php
2 namespace phinde;
3
4 /**
5  * Database table containing information about Pubsubhubbub subscriptions
6  */
7 class Subscriptions
8 {
9     protected $db;
10
11     public function __construct()
12     {
13         $this->db = new \PDO(
14             $GLOBALS['phinde']['db_dsn'],
15             $GLOBALS['phinde']['db_user'],
16             $GLOBALS['phinde']['db_pass']
17         );
18         $this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 
19     }
20
21     /**
22      * Fetch a topic
23      *
24      * @param string $topic Topic URL
25      *
26      * @return false|object False if the row does not exist
27      */
28     public function get($topic)
29     {
30         $stmt = $this->db->prepare(
31             'SELECT * FROM subscriptions'
32             . ' WHERE sub_topic = :topic'
33         );
34         $stmt->execute([':topic' => $topic]);
35
36         //fetchObject() itself returns FALSE on failure
37         return $stmt->fetchObject();
38     }
39
40     /**
41      * Remove a topic
42      *
43      * @param string $topic Topic URL
44      *
45      * @return void
46      */
47     public function remove($topic)
48     {
49         $stmt = $this->db->prepare(
50             'DELETE FROM subscriptions'
51             . ' WHERE sub_topic = :topic'
52         );
53         $stmt->execute([':topic' => $topic]);
54     }
55
56     /**
57      * Count number of subscriptions
58      *
59      * @return array Array of keys with different status, number as value
60      */
61     public function count()
62     {
63         $stmt = $this->db->prepare(
64             'SELECT COUNT(*) as count, sub_status FROM subscriptions'
65             . ' GROUP BY sub_status'
66             . ' ORDER BY sub_status'
67         );
68         $stmt->execute();
69
70         $res = [];
71         foreach ($stmt as $row) {
72             $res[$row['sub_status']] = $row['count'];
73         }
74
75         return $res;
76     }
77
78     /**
79      * Get all topics that either expired or expire soon
80      *
81      * @return \PDOStatement Result iterator
82      */
83     public function getExpiring()
84     {
85         $stmt = $this->db->prepare(
86             'SELECT * FROM subscriptions'
87             . ' WHERE'
88             . '('
89             //expire soon
90             . '  sub_status IN ("active", "expired")'
91             . '  AND DATEDIFF(sub_expires, NOW()) <= 2'
92             . ') OR ('
93             //no reaction to subscription within 10 minutes
94             . '  sub_status = "subscribing"'
95             . '  AND TIMEDIFF(NOW(), sub_created) > "00:10:00"'
96             . ')'
97         );
98         $stmt->execute();
99
100         return $stmt;
101     }
102
103     /**
104      * Create a new subscription entry in database.
105      * Automatically generates secret, capkey and lease seconds.
106      *
107      * This method does NOT:
108      * - check for duplicates (do it yourself)
109      * - return the object (fetch it yourself)
110      * - send subscription requests to the hub
111      *
112      * @param string $topic URL to subscribe to
113      * @param string $hub   URL of the hub subscribing to
114      *
115      * @return void
116      */
117     public function create($topic, $hub)
118     {
119         $stmt = $this->db->prepare(
120             'INSERT INTO subscriptions'
121             . ' (sub_topic, sub_status, sub_lease_seconds, sub_expires'
122             . ', sub_secret, sub_capkey, sub_hub, sub_created, sub_updated'
123             . ', sub_pings, sub_lastping, sub_statusmessage)'
124             . ' VALUES '
125             . ' (:topic, "subscribing", :lease_seconds, "0000-00-00 00:00:00"'
126             . ', :secret, :capkey, :hub, NOW(), NOW()'
127             . ', 0, "0000-00-00 00:00:00", "")'
128         );
129         $stmt->execute(
130             [
131                 ':topic'         => $topic,
132                 ':lease_seconds' => 86400 * 30,
133                 ':secret'        => bin2hex(openssl_random_pseudo_bytes(16)),
134                 ':capkey'        => bin2hex(openssl_random_pseudo_bytes(16)),
135                 ':hub'           => $hub,
136             ]
137         );
138     }
139
140     /**
141      * Renew a subscription: Set its status to "subscribing"
142      *
143      * @param integer $subId Subscription ID
144      *
145      * @return void
146      */
147     public function renew($subId)
148     {
149         $this->db->prepare(
150             'UPDATE subscriptions'
151             . ' SET sub_status  = "subscribing"'
152             . '   , sub_updated = NOW()'
153             . ' WHERE sub_id = :id'
154         )->execute([':id' => $subId]);
155     }
156
157     /**
158      * A subscription has been confirmed by the hub - mark it as active.
159      *
160      * @param integer $subId        Subscription ID
161      * @param integer $leaseSeconds Number of seconds until subscription expires
162      *
163      * @return void
164      */
165     public function subscribed($subId, $leaseSeconds)
166     {
167         $this->db->prepare(
168             'UPDATE subscriptions'
169             . ' SET sub_status        = "active"'
170             . '   , sub_lease_seconds = :leaseSeconds'
171             . '   , sub_expires       = :expires'
172             . '   , sub_updated       = NOW()'
173             . ' WHERE sub_id = :id'
174         )->execute(
175             [
176                 ':leaseSeconds' => $leaseSeconds,
177                 ':expires' => gmdate('Y-m-d H:i:s', time() + $leaseSeconds),
178                 ':id' => $subId,
179             ]
180         );
181     }
182
183     /**
184      * Mark a subscription as "unsubscribed"
185      *
186      * @param integer $subId Subscription ID
187      *
188      * @return void
189      */
190     public function unsubscribed($subId)
191     {
192         $this->db->prepare(
193             'UPDATE subscriptions'
194             . ' SET sub_status = "unsubscribed"'
195             . '   , sub_updated = NOW()'
196             . ' WHERE sub_id = :id'
197         )->execute([':id' => $subId]);
198     }
199
200     /**
201      * Subscription has been cancelled/denied for some reason
202      *
203      * @param integer $subId  Subscription ID
204      * @param string  $reason Cancellation reason
205      *
206      * @return void
207      */
208     public function denied($subId, $reason)
209     {
210         $this->db->prepare(
211             'UPDATE subscriptions'
212             . ' SET sub_status = "denied"'
213             . '   , sub_statusmessage = :reason'
214             . '   , sub_updated = NOW()'
215             . ' WHERE sub_id = :id'
216         )->execute([':id' => $subId, ':reason' => $reason]);
217     }
218
219     /**
220      * Topic update notification has been received
221      *
222      * @param integer $subId  Subscription ID
223      *
224      * @return void
225      */
226     public function pinged($subId)
227     {
228         $this->db->prepare(
229             'UPDATE subscriptions'
230             . ' SET sub_pings    = sub_pings + 1'
231             . '   , sub_lastping = NOW()'
232             . '   , sub_updated  = NOW()'
233             . ' WHERE sub_id = :id'
234         )->execute([':id' => $subId]);
235     }
236
237     /**
238      * Detect the hub for the given topic URL
239      *
240      * @param string $url Topic URL
241      *
242      * @return array Topic URL and hub URL. Hub URL is NULL if there is none.
243      */
244     public function detectHub($url)
245     {
246         $hue = new HubUrlExtractor();
247         $hue->setRequestTemplate(new HttpRequest());
248         $urls = $hue->getUrls($url);
249         //we violate the spec by not requiring a self URL
250         $topicUrl = isset($urls['self']) ? $urls['self'] : $url;
251         $hubUrl   = isset($urls['hub'])  ? $urls['hub'] : null;
252
253         return array($topicUrl, $hubUrl);
254     }
255 }
256 ?>