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

ALERT::add()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 121
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 27.6558

Importance

Changes 0
Metric Value
cc 10
eloc 46
c 0
b 0
f 0
nc 16
nop 3
dl 0
loc 121
rs 7.3115
ccs 18
cts 41
cp 0.439
crap 27.6558

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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