Passed
Pull Request — master (#1700)
by Struan
04:10
created

ALERT   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 429
Duplicated Lines 0 %

Test Coverage

Coverage 63.89%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 196
c 1
b 0
f 0
dl 0
loc 429
rs 5.5199
ccs 115
cts 180
cp 0.6389
wmc 56

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A fetch_between() 0 23 2
A email() 0 1 1
A criteria() 0 1 1
B check_token() 0 35 7
B add() 0 116 8
A fetch() 0 25 1
A suspend() 0 9 2
B send_confirmation_email() 0 40 7
A send_already_signedup_email() 0 18 2
A email_exists() 0 15 3
A delete() 0 9 2
A fetch_by_mp() 0 12 2
A fetch_by_token() 0 16 2
A delete_all() 0 9 2
B criteria_pretty() 0 17 9
A confirm() 0 11 2
A resume() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like ALERT often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ALERT, and based on these observations, apply Extract Interface, too.

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
        // After we've add()ed an alert we'll be sending them
232
        // a confirmation email with a link to confirm their address.
233
        // $details is the array we just sent to add(), and which it's
234
        // passed on to us here.
235
        // A brief check of the facts...
236
        if (!is_numeric($this->alert_id) ||
237
            !isset($details['email']) ||
238
            $details['email'] == '') {
239
            return false;
240
        }
241
242
        // We prefix the registration token with the alert's id and '-'.
243
        // Not for any particularly good reason, but we do.
244
245
        $urltoken = $this->alert_id . '-' . $this->registrationtoken;
246
247
        if ( isset($details['confirm_base']) && $details['confirm_base'] !== '' ) {
248
            $confirmurl = $details['confirm_base'] . $urltoken;
249
        } else {
250
            $confirmurl = 'https://' . DOMAIN . '/A/' . $urltoken;
251
        }
252
253
        // Arrays we need to send a templated email.
254
        $data = array (
255
            'to' 		=> $details['email'],
256
            'template' 	=> 'alert_confirmation'
257
        );
258
259
        $merge = array (
260
            'CONFIRMURL'	=> $confirmurl,
261
            'CRITERIA'	=> $this->criteria_pretty()
262
        );
263
264
        $success = send_template_email($data, $merge);
265
        if ($success) {
266
            return true;
267
        } else {
268
            return false;
269
        }
270
    }
271
272
    public function send_already_signedup_email($details) {
273
        $data = array (
274
            'to' 		=> $details['email'],
275
            'template' 	=> 'alert_already_signedup'
276
        );
277
278
        $criteria = \MySociety\TheyWorkForYou\Utility\Alert::detailsToCriteria($details);
279
        $this->criteria = $criteria;
280
281
        $merge = array (
282
            'CRITERIA'	=> $this->criteria_pretty()
283
        );
284
285
        $success = send_template_email($data, $merge);
286
        if ($success) {
287
            return true;
288
        } else {
289
            return false;
290
        }
291
    }
292
293 2
    public function fetch_by_mp($email, $pid) {
294 2
        $q = $this->db->query("SELECT alert_id FROM alerts
295
            WHERE confirmed AND NOT deleted
296
            AND email = :email
297
            AND criteria = :criteria", array(
298 2
                ':email' => $email,
299 2
                ':criteria' => 'speaker:' . $pid
300
            ));
301 2
        if ($q->rows() > 0) {
302 1
            return true;
303
        } else {
304 1
            return false;
305
        }
306
    }
307
308 2
    public function email_exists($email) {
309
        // Returns true if there's a user with this email address.
310
311 2
        if ($email != "") {
312 2
            $q = $this->db->query("SELECT alert_id FROM alerts
313
                WHERE email = :email", array(
314 2
                    ':email' => $email
315
                ));
316 2
            if ($q->rows() > 0) {
317 1
                return true;
318
            } else {
319 1
                return false;
320
            }
321
        } else {
322
            return false;
323
        }
324
325
    }
326
327 12
    public function check_token($token) {
328 12
        if (!is_null($this->token_checked)) {
329
            return $this->token_checked;
330
        }
331
332 12
        $arg = strstr($token, '::') ? '::' : '-';
333 12
        $token_parts = explode($arg, $token);
334 12
        if (count($token_parts) != 2) {
335 2
            return false;
336
        }
337
338 10
        list($alert_id, $registrationtoken) = $token_parts;
339 10
        if (!is_numeric($alert_id) || !$registrationtoken) {
340
            return false;
341
        }
342
343 10
        $q = $this->db->query("SELECT alert_id, email, criteria
344
                        FROM alerts
345
                        WHERE alert_id = :alert_id
346
                        AND registrationtoken = :registration_token
347
                        ", array(
348 10
                            ':alert_id' => $alert_id,
349 10
                            ':registration_token' => $registrationtoken
350 10
                        ))->first();
351 10
        if (!$q) {
352 5
            $this->token_checked = false;
353
        } else {
354 5
            $this->token_checked = array(
355 5
                'id' => $q['alert_id'],
356 5
                'email' => $q['email'],
357 5
                'criteria' => $q['criteria'],
358
            );
359
        }
360
361 10
        return $this->token_checked;
362
    }
363
364
    public function fetch_by_token($confirmation) {
365
        $q = $this->db->query("SELECT alert_id, email, criteria
366
                        FROM alerts
367
                        WHERE registrationtoken = :registration_token
368
                        ", array(
369
                            ':registration_token' => $confirmation
370
                        )
371
                    )->first();
372
373
        if (!$q) {
374
            return false;
375
        } else {
376
            return array(
377
                'id' => $q['alert_id'],
378
                'email' => $q['email'],
379
                'criteria' => $q['criteria'],
380
            );
381
        }
382
    }
383
384
    // The user has clicked the link in their confirmation email
385
    // and the confirm page has passed the token from the URL to here.
386
    // If all goes well the alert will be confirmed.
387
    // The alert will be active when scripts run each day to send the actual emails.
388 2
    public function confirm($token) {
389 2
        if (!($alert = $this->check_token($token))) {
390 1
            return false;
391
        }
392 1
        $this->criteria = $alert['criteria'];
393 1
        $this->email = $alert['email'];
394 1
        $r = $this->db->query("UPDATE alerts SET confirmed = 1, deleted = 0 WHERE alert_id = :alert_id", array(
395 1
            ':alert_id' => $alert['id']
396
            ));
397
398 1
        return $r->success();
399
    }
400
401
    // The user has clicked the link in their delete confirmation email
402
    // and the deletion page has passed the token from the URL to here.
403
    // If all goes well the alert will be deleted.
404 2
    public function delete($token) {
405 2
        if (!($alert = $this->check_token($token))) {
406 1
            return false;
407
        }
408 1
        $r = $this->db->query("DELETE FROM alerts WHERE alert_id = :alert_id", array(
409 1
            ':alert_id' => $alert['id']
410
            ));
411
412 1
        return $r->success();
413
    }
414
415
    public function delete_all($token) {
416
        if (!($alert = $this->check_token($token))) {
417
            return false;
418
        }
419
        $r = $this->db->query("DELETE FROM alerts WHERE email = :email", array(
420
            ':email' => $alert['email']
421
            ));
422
423
        return $r->success();
424
    }
425
426 2
    public function suspend($token) {
427 2
        if (!($alert = $this->check_token($token))) {
428 1
            return false;
429
        }
430 1
        $r = $this->db->query("UPDATE alerts SET deleted = 2 WHERE alert_id = :alert_id", array(
431 1
            ':alert_id' => $alert['id']
432
            ));
433
434 1
        return $r->success();
435
    }
436
437 2
    public function resume($token) {
438 2
        if (!($alert = $this->check_token($token))) {
439 1
            return false;
440
        }
441 1
        $r = $this->db->query("UPDATE alerts SET deleted = 0 WHERE alert_id = :alert_id", array(
442 1
            ':alert_id' => $alert['id']
443
            ));
444
445 1
        return $r->success();
446
    }
447
448
    // Getters
449
    public function email() { return $this->email; }
450
    public function criteria() { return $this->criteria; }
451
    public function criteria_pretty($html = false) {
452
        $criteria = explode(' ',$this->criteria);
453
        $spokenby = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($this->criteria));
454
        $words = array();
455
        foreach ($criteria as $c) {
456
            if (!preg_match('#^speaker:(\d+)#',$c,$m)) {
457
                $words[] = $c;
458
            }
459
        }
460
        $criteria = '';
461
        if (count($words)) {
462
            $criteria .= ($html?'<li>':'* ') . sprintf(gettext('Mentions of [%s]'), implode(' ', $words)) . ($html?'</li>':'') . "\n";
463
        }
464
        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...
465
            $criteria .= ($html?'<li>':'* ') . sprintf(gettext("Things by %s"), implode(' or ', $spokenby)) . ($html?'</li>':'') . "\n";
466
        }
467
        return $criteria;
468
    }
469
470
}
471