Failed Conditions
Pull Request — master (#1851)
by Struan
04:38
created

ALERT::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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