e49e817549d811981fcfa362584e18129e0a68c5
[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      *
114      * @return void
115      */
116     public function create($topic)
117     {
118         $stmt = $this->db->prepare(
119             'INSERT INTO subscriptions'
120             . ' (sub_topic, sub_status, sub_lease_seconds, sub_expires'
121             . ', sub_secret, sub_capkey, sub_created, sub_updated'
122             . ', sub_pings, sub_lastping, sub_statusmessage)'
123             . ' VALUES '
124             . ' (:topic, "subscribing", :lease_seconds, "0000-00-00 00:00:00"'
125             . ', :secret, :capkey, NOW(), NOW()'
126             . ', 0, "0000-00-00 00:00:00", "")'
127         );
128         $stmt->execute(
129             [
130                 ':topic'         => $topic,
131                 ':lease_seconds' => 86400 * 30,
132                 ':secret'        => bin2hex(openssl_random_pseudo_bytes(16)),
133                 ':capkey'        => bin2hex(openssl_random_pseudo_bytes(16)),
134             ]
135         );
136     }
137
138     /**
139      * Renew a subscription: Set its status to "subscribing"
140      *
141      * @param integer $subId Subscription ID
142      *
143      * @return void
144      */
145     public function renew($subId)
146     {
147         $this->db->prepare(
148             'UPDATE subscriptions'
149             . ' SET sub_status  = "subscribing"'
150             . '   , sub_updated = NOW()'
151             . ' WHERE sub_id = :id'
152         )->execute([':id' => $subId]);
153     }
154
155     /**
156      * A subscription has been confirmed by the hub - mark it as active.
157      *
158      * @param integer $subId        Subscription ID
159      * @param integer $leaseSeconds Number of seconds until subscription expires
160      *
161      * @return void
162      */
163     public function subscribed($subId, $leaseSeconds)
164     {
165         $this->db->prepare(
166             'UPDATE subscriptions'
167             . ' SET sub_status        = "active"'
168             . '   , sub_lease_seconds = :leaseSeconds'
169             . '   , sub_expires       = :expires'
170             . '   , sub_updated       = NOW()'
171             . ' WHERE sub_id = :id'
172         )->execute(
173             [
174                 ':leaseSeconds' => $leaseSeconds,
175                 ':expires' => gmdate('Y-m-d H:i:s', time() + $leaseSeconds),
176                 ':id' => $subId,
177             ]
178         );
179     }
180
181     /**
182      * Mark a subscription as "unsubscribed"
183      *
184      * @param integer $subId Subscription ID
185      *
186      * @return void
187      */
188     public function unsubscribed($subId)
189     {
190         $this->db->prepare(
191             'UPDATE subscriptions'
192             . ' SET sub_status = "unsubscribed"'
193             . '   , sub_updated = NOW()'
194             . ' WHERE sub_id = :id'
195         )->execute([':id' => $subId]);
196     }
197
198     /**
199      * Subscription has been cancelled/denied for some reason
200      *
201      * @param integer $subId  Subscription ID
202      * @param string  $reason Cancellation reason
203      *
204      * @return void
205      */
206     public function denied($subId, $reason)
207     {
208         $this->db->prepare(
209             'UPDATE subscriptions'
210             . ' SET sub_status = "denied"'
211             . '   , sub_statusmessage = :reason'
212             . '   , sub_updated = NOW()'
213             . ' WHERE sub_id = :id'
214         )->execute([':id' => $subId, ':reason' => $reason]);
215     }
216
217     /**
218      * Topic update notification has been received
219      *
220      * @param integer $subId  Subscription ID
221      *
222      * @return void
223      */
224     public function pinged($subId)
225     {
226         $this->db->prepare(
227             'UPDATE subscriptions'
228             . ' SET sub_pings    = sub_pings + 1'
229             . '   , sub_lastping = NOW()'
230             . '   , sub_updated  = NOW()'
231             . ' WHERE sub_id = :id'
232         )->execute([':id' => $subId]);
233     }
234
235     /**
236      * Detect the hub for the given topic URL
237      *
238      * @param string $url Topic URL
239      *
240      * @return array Topic URL and hub URL. Hub URL is NULL if there is none.
241      */
242     public function detectHub($url)
243     {
244         $hue = new HubUrlExtractor();
245         $hue->setRequestTemplate(new HttpRequest());
246         $urls = $hue->getUrls($url);
247         //we violate the spec by not requiring a self URL
248         $topicUrl = isset($urls['self']) ? $urls['self'] : $url;
249         $hubUrl   = isset($urls['hub'])  ? $urls['hub'] : null;
250
251         return array($topicUrl, $hubUrl);
252     }
253 }
254 ?>