Passed
Push — master ( d8531e...e5f489 )
by Matthew
04:14
created

ALERT::email_exists()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 1
dl 0
loc 15
ccs 7
cts 8
cp 0.875
crap 3.0175
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
/*
4
5
NO HTML IN THIS FILE!!
6
7
// Name: alert.php
8
// Author:  Richard Allan [email protected]
9
// Version: 0.5 beta
10
// Date: 6th Jan 2005
11
// Description:  This file contains ALERT class.
12
13
The ALERT class allows us to fetch and alter data about any email alert.
14
Functions here:
15
16
ALERT
17
18
    fetch_between($confirmed, $deleted, $start_date, $end_date)	Fetch summary data on alerts created between the dates.
19
    fetch($confirmed, $deleted)					Fetch all alert data from DB.
20
    add($details, $confirmation_email)				Add a new alert to the DB.
21
    send_confirmation_email($details)				Done after add()ing the alert.
22
    email_exists($email)						Checks if an alert exists with a certain email address.
23
    confirm($token)							Confirm a new alert in the DB
24
    delete($token)							Remove an existing alert from the DB
25
    id_exists()							Checks if an alert_id is valid.
26
27
To create a new alert do:
28
    $ALERT = new ALERT;
29
    $ALERT->add();
30
31
You can then access all the alert's variables with appropriately named functions, such as:
32
    $ALERT->email();
33
etc.
34
35
*/
36
37
// CLASS:  ALERT
38
39
class ALERT {
40
41
    public $token_checked = null;
42
    private $alert_id = "";
43
    public $email = "";
44
    public $criteria = "";		// Sets the terms that are used to produce the search results.
45
46
    private $db;
47
48 22
    public function __construct() {
49 22
        $this->db = new ParlDB;
50 22
    }
51
52
// FUNCTION: fetch_between
53
54 1
    public function fetch_between($confirmed, $deleted, $start_date, $end_date) {
55
        // Return summary data on all the alerts that were created between $start_date
56
        // and $end_date (inclusive) and whose confirmed and deleted values match the booleans
57
        // passed in $confirmed and $deleted
58 1
        $q = $this->db->query("SELECT   criteria, count(*) as cnt
59
            FROM     alerts
60
            WHERE    confirmed = :confirmed
61
            AND      deleted = :deleted
62
            AND      created >= :start_date
63
            AND      created <= :end_date
64
            GROUP BY criteria", array(
65 1
            ':confirmed' => $confirmed,
66 1
            ':deleted' => $deleted,
67 1
            ':start_date' => $start_date,
68 1
            ':end_date' => $end_date
69
            ));
70 1
        $data = array();
71 1
        foreach ($q as $row) {
72 1
            $contents = array('criteria' => $row['criteria'], 'count' => $row['cnt']);
73 1
            $data[] = $contents;
74
        }
75 1
        $data = array ('alerts' => $data);
76 1
        return $data;
77
    }
78
79
// FUNCTION: fetch
80
81 2
    public function fetch($confirmed, $deleted) {
82
        // Pass it an alert id and it will fetch data about alerts from the db
83
        // and put it all in the appropriate variables.
84
        // Normal usage is for $confirmed variable to be set to true
85
        // and $deleted variable to be set to false
86
        // so that only live confirmed alerts are chosen.
87
88
        // Look for this alert_id's details.
89 2
        $q = $this->db->query("SELECT alert_id,
90
                        email,
91
                        criteria,
92
                        registrationtoken,
93
                        lang,
94
                        deleted,
95
                        confirmed
96
                        FROM alerts
97 2
                        WHERE confirmed =" . $confirmed .
98 2
                        " AND deleted=" . $deleted .
99 2
                        ' ORDER BY email');
100
101 2
        $data = $q->fetchAll();
102 2
        $info = "Alert";
103 2
        $data = array ('info' => $info, 'data' => $data);
104
105 2
        return $data;
106
    }
107
108 3
    public function add($details, $confirmation_email=false, $instantly_confirm=true) {
109
110
        // Adds a new alert's info into the database.
111
        // Then calls another function to send them a confirmation email.
112
        // $details is an associative array of all the alert's details, of the form:
113
        // array (
114
        //		"email" => "[email protected]",
115
        //		"criteria"	=> "speaker:521",
116
        //		etc... using the same keys as the object variable names.
117
        // )
118
119 3
        $criteria = \MySociety\TheyWorkForYou\Utility\Alert::detailsToCriteria($details);
120
121 3
        $q = $this->db->query("SELECT * FROM alerts
122
            WHERE email = :email
123
            AND criteria = :criteria
124
            AND confirmed=1", array(
125 3
                ':email' => $details['email'],
126 3
                ':criteria' => $criteria
127 3
            ))->first();
128 3
        if ($q) {
129 2
            if ($q['deleted']) {
130 1
                $this->db->query("UPDATE alerts SET deleted=0
131
                    WHERE email = :email
132
                    AND criteria = :criteria
133
                    AND confirmed=1", array(
134 1
                        ':email' => $details['email'],
135 1
                        ':criteria' => $criteria
136
                    ));
137 1
                return 1;
138
            } else {
139 1
                return -2;
140
            }
141
        }
142
143 1
        $q = $this->db->query("INSERT INTO alerts (
144
                email, criteria, postcode, lang, deleted, confirmed, created
145
            ) VALUES (
146
                :email,
147
                :criteria,
148
                :pc,
149
                :lang,
150
                '0', '0', NOW()
151
            )
152
        ", array(
153 1
            ':email' => $details['email'],
154 1
            ':criteria' => $criteria,
155 1
            ':pc' => $details['pc'],
156
            ':lang' => LANGUAGE,
157
            ));
158
159 1
        if ($q->success()) {
160
161
            // Get the alert id so that we can perform the updates for confirmation
162
163 1
            $this->alert_id = $q->insert_id();
164 1
            $this->criteria = $criteria;
165
166
            // We have to set the alert's registration token.
167
            // This will be sent to them via email, so we can confirm they exist.
168
            // The token will be the first 16 characters of a hash.
169
170
            // This gives a code for their email address which is then joined
171
            // to the timestamp so as to provide a unique ID for each alert.
172
173 1
            $token = substr( password_hash($details["email"] . microtime(), PASSWORD_BCRYPT), 29, 16 );
174
175
            // Full stops don't work well at the end of URLs in emails, so
176
            // replace them. And double slash would be treated as single and
177
            // not work either. We won't be doing anything clever with the hash
178
            // stuff, just need to match this token.
179 1
            $token = strtr($token, './', 'Xx');
180 1
            $this->registrationtoken = $token;
0 ignored issues
show
Bug Best Practice introduced by
The property registrationtoken does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
181
182
            // Add that to the database.
183
184 1
            $r = $this->db->query("UPDATE alerts
185
                        SET registrationtoken = :registration_token
186
                        WHERE alert_id = :alert_id
187
                        ", array(
188 1
                            ':registration_token' => $this->registrationtoken,
189 1
                            ':alert_id' => $this->alert_id
190
                        ));
191
192 1
            if ($r->success()) {
193
                // Updated DB OK.
194
195 1
                if ($confirmation_email) {
196
                    // Right, send the email...
197
                    $success = $this->send_confirmation_email($details);
198
199
                    if ($success) {
200
                        // Email sent OK
201
                        return 1;
202
                    } else {
203
                        // Couldn't send the email.
204
                        return -1;
205
                    }
206 1
                } elseif ($instantly_confirm) {
207
                    // No confirmation email needed.
208 1
                    $this->db->query("UPDATE alerts
209
                        SET confirmed = '1'
210
                        WHERE alert_id = :alert_id
211
                        ", array(
212 1
                            ':alert_id' => $this->alert_id
213
                        ));
214 1
                    return 1;
215
                }
216
            } else {
217
                // Couldn't add the registration token to the DB.
218
                return -1;
219
            }
220
221
        } else {
222
            // Couldn't add the user's data to the DB.
223
            return -1;
224
        }
225
    }
226
227
// FUNCTION:  send_confirmation_email
228
229
    public function send_confirmation_email($details) {
230
231
        if (in_array($details['email'], ALERT_NO_EMAIL)) {
0 ignored issues
show
Bug introduced by
The constant ALERT_NO_EMAIL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
232
            return false;
233
        }
234
235
        // After we've add()ed an alert we'll be sending them
236
        // a confirmation email with a link to confirm their address.
237
        // $details is the array we just sent to add(), and which it's
238
        // passed on to us here.
239
        // A brief check of the facts...
240
        if (!is_numeric($this->alert_id) ||
241
            !isset($details['email']) ||
242
            $details['email'] == '') {
243
            return false;
244
        }
245
246
        // We prefix the registration token with the alert's id and '-'.
247
        // Not for any particularly good reason, but we do.
248
249
        $urltoken = $this->alert_id . '-' . $this->registrationtoken;
250
251
        if ( isset($details['confirm_base']) && $details['confirm_base'] !== '' ) {
252
            $confirmurl = $details['confirm_base'] . $urltoken;
253
        } else {
254
            $confirmurl = 'https://' . DOMAIN . '/A/' . $urltoken;
255
        }
256
257
        // Arrays we need to send a templated email.
258
        $data = array (
259
            'to' 		=> $details['email'],
260
            'template' 	=> 'alert_confirmation'
261
        );
262
263
        $merge = array (
264
            'CONFIRMURL'	=> $confirmurl,
265
            'CRITERIA'	=> $this->criteria_pretty()
266
        );
267
268
        $success = send_template_email($data, $merge);
269
        if ($success) {
270
            return true;
271
        } else {
272
            return false;
273
        }
274
    }
275
276
    public function send_already_signedup_email($details) {
277
        $data = array (
278
            'to' 		=> $details['email'],
279
            'template' 	=> 'alert_already_signedup'
280
        );
281
282
        $criteria = \MySociety\TheyWorkForYou\Utility\Alert::detailsToCriteria($details);
283
        $this->criteria = $criteria;
284
285
        $merge = array (
286
            'CRITERIA'	=> $this->criteria_pretty()
287
        );
288
289
        $success = send_template_email($data, $merge);
290
        if ($success) {
291
            return true;
292
        } else {
293
            return false;
294
        }
295
    }
296
297 2
    public function fetch_by_mp($email, $pid) {
298 2
        $q = $this->db->query("SELECT alert_id FROM alerts
299
            WHERE confirmed AND NOT deleted
300
            AND email = :email
301
            AND criteria = :criteria", array(
302 2
                ':email' => $email,
303 2
                ':criteria' => 'speaker:' . $pid
304
            ));
305 2
        if ($q->rows() > 0) {
306 1
            return true;
307
        } else {
308 1
            return false;
309
        }
310
    }
311
312 2
    public function email_exists($email) {
313
        // Returns true if there's a user with this email address.
314
315 2
        if ($email != "") {
316 2
            $q = $this->db->query("SELECT alert_id FROM alerts
317
                WHERE email = :email", array(
318 2
                    ':email' => $email
319
                ));
320 2
            if ($q->rows() > 0) {
321 1
                return true;
322
            } else {
323 1
                return false;
324
            }
325
        } else {
326
            return false;
327
        }
328
329
    }
330
331 12
    public function check_token($token) {
332 12
        if (!is_null($this->token_checked)) {
333
            return $this->token_checked;
334
        }
335
336 12
        $arg = strstr($token, '::') ? '::' : '-';
337 12
        $token_parts = explode($arg, $token);
338 12
        if (count($token_parts) != 2) {
339 2
            return false;
340
        }
341
342 10
        list($alert_id, $registrationtoken) = $token_parts;
343 10
        if (!is_numeric($alert_id) || !$registrationtoken) {
344
            return false;
345
        }
346
347 10
        $q = $this->db->query("SELECT alert_id, email, criteria
348
                        FROM alerts
349
                        WHERE alert_id = :alert_id
350
                        AND registrationtoken = :registration_token
351
                        ", array(
352 10
                            ':alert_id' => $alert_id,
353 10
                            ':registration_token' => $registrationtoken
354 10
                        ))->first();
355 10
        if (!$q) {
356 5
            $this->token_checked = false;
357
        } else {
358 5
            $this->token_checked = array(
359 5
                'id' => $q['alert_id'],
360 5
                'email' => $q['email'],
361 5
                'criteria' => $q['criteria'],
362
            );
363
        }
364
365 10
        return $this->token_checked;
366
    }
367
368
    public function fetch_by_token($confirmation) {
369
        $q = $this->db->query("SELECT alert_id, email, criteria
370
                        FROM alerts
371
                        WHERE registrationtoken = :registration_token
372
                        ", array(
373
                            ':registration_token' => $confirmation
374
                        )
375
                    )->first();
376
377
        if (!$q) {
378
            return false;
379
        } else {
380
            return array(
381
                'id' => $q['alert_id'],
382
                'email' => $q['email'],
383
                'criteria' => $q['criteria'],
384
            );
385
        }
386
    }
387
388
    // The user has clicked the link in their confirmation email
389
    // and the confirm page has passed the token from the URL to here.
390
    // If all goes well the alert will be confirmed.
391
    // The alert will be active when scripts run each day to send the actual emails.
392 2
    public function confirm($token) {
393 2
        if (!($alert = $this->check_token($token))) {
394 1
            return false;
395
        }
396 1
        $this->criteria = $alert['criteria'];
397 1
        $this->email = $alert['email'];
398 1
        $r = $this->db->query("UPDATE alerts SET confirmed = 1, deleted = 0 WHERE alert_id = :alert_id", array(
399 1
            ':alert_id' => $alert['id']
400
            ));
401
402 1
        return $r->success();
403
    }
404
405
    // The user has clicked the link in their delete confirmation email
406
    // and the deletion page has passed the token from the URL to here.
407
    // If all goes well the alert will be deleted.
408 2
    public function delete($token) {
409 2
        if (!($alert = $this->check_token($token))) {
410 1
            return false;
411
        }
412 1
        $r = $this->db->query("DELETE FROM alerts WHERE alert_id = :alert_id", array(
413 1
            ':alert_id' => $alert['id']
414
            ));
415
416 1
        return $r->success();
417
    }
418
419
    public function delete_all($token) {
420
        if (!($alert = $this->check_token($token))) {
421
            return false;
422
        }
423
        $r = $this->db->query("DELETE FROM alerts WHERE email = :email", array(
424
            ':email' => $alert['email']
425
            ));
426
427
        return $r->success();
428
    }
429
430 2
    public function suspend($token) {
431 2
        if (!($alert = $this->check_token($token))) {
432 1
            return false;
433
        }
434 1
        $r = $this->db->query("UPDATE alerts SET deleted = 2 WHERE alert_id = :alert_id", array(
435 1
            ':alert_id' => $alert['id']
436
            ));
437
438 1
        return $r->success();
439
    }
440
441 2
    public function resume($token) {
442 2
        if (!($alert = $this->check_token($token))) {
443 1
            return false;
444
        }
445 1
        $r = $this->db->query("UPDATE alerts SET deleted = 0 WHERE alert_id = :alert_id", array(
446 1
            ':alert_id' => $alert['id']
447
            ));
448
449 1
        return $r->success();
450
    }
451
452
    // Getters
453
    public function email() { return $this->email; }
454
    public function criteria() { return $this->criteria; }
455
    public function criteria_pretty($html = false) {
456
        $criteria = explode(' ',$this->criteria);
457
        $spokenby = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($this->criteria));
458
        $words = array();
459
        foreach ($criteria as $c) {
460
            if (!preg_match('#^speaker:(\d+)#',$c,$m)) {
461
                $words[] = $c;
462
            }
463
        }
464
        $criteria = '';
465
        if (count($words)) {
466
            $criteria .= ($html?'<li>':'* ') . sprintf(gettext('Mentions of [%s]'), implode(' ', $words)) . ($html?'</li>':'') . "\n";
467
        }
468
        if ($spokenby) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $spokenby of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
469
            $criteria .= ($html?'<li>':'* ') . sprintf(gettext("Things by %s"), implode(' or ', $spokenby)) . ($html?'</li>':'') . "\n";
470
        }
471
        return $criteria;
472
    }
473
474
}
475