Failed Conditions
Pull Request — master (#1846)
by Struan
54:09 queued 22:02
created

ALERT::update()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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