Failed Conditions
Branch master (3ce7e2)
by Nick
14:43
created

ALERT::fetch_by_mp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 8
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 12
rs 9.4285
ccs 8
cts 8
cp 1
crap 2
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 {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
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 1
            GROUP BY criteria", array(
65 1
            ':confirmed' => $confirmed,
66 1
            ':deleted' => $deleted,
67 1
            ':start_date' => $start_date,
68
            ':end_date' => $end_date
69 1
            ));
70 1
        $data = array();
71 1
    for ($row=0; $row<$q->rows(); $row++) {
72 1
      $contents = array('criteria' => $q->field($row, 'criteria'), 'count' => $q->field($row, 'cnt'));
73 1
          $data[] = $contents;
74 1
    }
75 1
    $data = array ('alerts' => $data);
76
77 1
    return $data;
78
    }
79
80
// FUNCTION: fetch
81
82 2
    public function fetch($confirmed, $deleted) {
83
        // Pass it an alert id and it will fetch data about alerts from the db
84
        // and put it all in the appropriate variables.
85
        // Normal usage is for $confirmed variable to be set to true
86
        // and $deleted variable to be set to false
87
        // so that only live confirmed alerts are chosen.
88
89
        // Look for this alert_id's details.
90 2
        $q = $this->db->query("SELECT alert_id,
91
                        email,
92
                        criteria,
93
                        registrationtoken,
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 = array();
102
103 2
            for ($row=0; $row<$q->rows(); $row++) {
104
                $contents = array(
105 2
                'alert_id' 	=> $q->field($row, 'alert_id'),
106 2
                'email' 	=> $q->field($row, 'email'),
107 2
                'criteria' 	=> $q->field($row, 'criteria'),
108 2
                'registrationtoken' => $q->field($row, 'registrationtoken'),
109 2
                'confirmed' 	=> $q->field($row, 'confirmed'),
110 2
                'deleted' 	=> $q->field($row, 'deleted')
111 2
            );
112 2
                $data[] = $contents;
113 2
            }
114 2
            $info = "Alert";
115 2
            $data = array ('info' => $info, 'data' => $data);
116
117
118 2
            return $data;
119
    }
120
121 3
    public function add($details, $confirmation_email=false, $instantly_confirm=true) {
122
123
        // Adds a new alert's info into the database.
124
        // Then calls another function to send them a confirmation email.
125
        // $details is an associative array of all the alert's details, of the form:
126
        // array (
127
        //		"email" => "[email protected]",
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
128
        //		"criteria"	=> "speaker:521",
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
129
        //		etc... using the same keys as the object variable names.
130
        // )
131
132 3
        $criteria = \MySociety\TheyWorkForYou\Utility\Alert::detailsToCriteria($details);
133
134 3
        $q = $this->db->query("SELECT * FROM alerts
135
            WHERE email = :email
136
            AND criteria = :criteria
137 3
            AND confirmed=1", array(
138 3
                ':email' => $details['email'],
139
                ':criteria' => $criteria
140 3
            ));
141 3
        if ($q->rows() > 0) {
142 2
            $deleted = $q->field(0, 'deleted');
143 2
            if ($deleted) {
144 1
                $this->db->query("UPDATE alerts SET deleted=0
145
                    WHERE email = :email
146
                    AND criteria = :criteria
147 1
                    AND confirmed=1", array(
148 1
                        ':email' => $details['email'],
149
                        ':criteria' => $criteria
150 1
                    ));
151 1
                return 1;
152
            } else {
153 1
                return -2;
154
            }
155
        }
156
157 1
        $q = $this->db->query("INSERT INTO alerts (
158
                email, criteria, postcode, deleted, confirmed, created
159
            ) VALUES (
160
                :email,
161
                :criteria,
162
                :pc,
163
                '0', '0', NOW()
164
            )
165 1
        ", array(
166 1
            ':email' => $details['email'],
167 1
            ':criteria' => $criteria,
168 1
            ':pc' => $details['pc'],
169 1
            ));
170
171 1
        if ($q->success()) {
172
173
            // Get the alert id so that we can perform the updates for confirmation
174
175 1
            $this->alert_id = $q->insert_id();
176 1
            $this->criteria = $criteria;
177
178
            // We have to set the alert's registration token.
179
            // This will be sent to them via email, so we can confirm they exist.
180
            // The token will be the first 16 characters of a hash.
181
182
            // This gives a code for their email address which is then joined
183
            // to the timestamp so as to provide a unique ID for each alert.
184
185 1
            $token = substr( password_hash($details["email"] . microtime(), PASSWORD_BCRYPT), 29, 16 );
186
187
            // Full stops don't work well at the end of URLs in emails,
188
            // so replace them. We won't be doing anything clever with the hash
189
            // stuff, just need to match this token.
190
191 1
            $this->registrationtoken = strtr($token, '.', 'X');
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...
192
193
            // Add that to the database.
194
195 1
            $r = $this->db->query("UPDATE alerts
196
                        SET registrationtoken = :registration_token
197
                        WHERE alert_id = :alert_id
198 1
                        ", array(
199 1
                            ':registration_token' => $this->registrationtoken,
200 1
                            ':alert_id' => $this->alert_id
201 1
                        ));
202
203 1
            if ($r->success()) {
204
                // Updated DB OK.
205
206 1
                if ($confirmation_email) {
207
                    // Right, send the email...
208
                    $success = $this->send_confirmation_email($details);
209
210
                    if ($success) {
211
                        // Email sent OK
212
                        return 1;
213
                    } else {
214
                        // Couldn't send the email.
215
                        return -1;
216
                    }
217 1
                } elseif ($instantly_confirm) {
218
                    // No confirmation email needed.
219 1
                    $s = $this->db->query("UPDATE alerts
0 ignored issues
show
Unused Code introduced by
The assignment to $s is dead and can be removed.
Loading history...
220
                        SET confirmed = '1'
221
                        WHERE alert_id = :alert_id
222 1
                        ", array(
223 1
                            ':alert_id' => $this->alert_id
224 1
                        ));
225 1
                    return 1;
226
                }
227
            } else {
228
                // Couldn't add the registration token to the DB.
229
                return -1;
230
            }
231
232
        } else {
233
            // Couldn't add the user's data to the DB.
234
            return -1;
235
        }
236
    }
237
238
// FUNCTION:  send_confirmation_email
239
240
    public function send_confirmation_email($details) {
241
242
        // After we've add()ed an alert we'll be sending them
243
        // a confirmation email with a link to confirm their address.
244
        // $details is the array we just sent to add(), and which it's
245
        // passed on to us here.
246
        // A brief check of the facts...
247
        if (!is_numeric($this->alert_id) ||
248
            !isset($details['email']) ||
249
            $details['email'] == '') {
250
            return false;
251
        }
252
253
        // We prefix the registration token with the alert's id and '-'.
254
        // Not for any particularly good reason, but we do.
255
256
        $urltoken = $this->alert_id . '-' . $this->registrationtoken;
257
258
        if ( isset($details['confirm_base']) && $details['confirm_base'] !== '' ) {
259
            $confirmurl = $details['confirm_base'] . $urltoken;
260
        } else {
261
            $confirmurl = 'https://' . DOMAIN . '/A/' . $urltoken;
1 ignored issue
show
Bug introduced by
The constant DOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
262
        }
263
264
        // Arrays we need to send a templated email.
265
        $data = array (
266
            'to' 		=> $details['email'],
267
            'template' 	=> 'alert_confirmation'
268
        );
269
270
        $merge = array (
271
            'FIRSTNAME' 	=> 'THEY WORK FOR YOU',
272
            'LASTNAME' 		=> ' ALERT CONFIRMATION',
273
            'CONFIRMURL'	=> $confirmurl,
274
            'CRITERIA'	=> $this->criteria_pretty()
275
        );
276
277
        $success = send_template_email($data, $merge);
278
        if ($success) {
279
            return true;
280
        } else {
281
            return false;
282
        }
283
    }
284
285
    public function send_already_signedup_email($details) {
286
        $data = array (
287
            'to' 		=> $details['email'],
288
            'template' 	=> 'alert_already_signedup'
289
        );
290
291
        $criteria = \MySociety\TheyWorkForYou\Utility\Alert::detailsToCriteria($details);
292
        $this->criteria = $criteria;
293
294
        $merge = array (
295
            'FIRSTNAME' 	=> 'THEY WORK FOR YOU',
296
            'LASTNAME' 		=> ' ALERT ALREADY SIGNED UP',
297
            'CRITERIA'	=> $this->criteria_pretty()
298
        );
299
300
        $success = send_template_email($data, $merge);
301
        if ($success) {
302
            return true;
303
        } else {
304
            return false;
305
        }
306
    }
307
308 2
    public function fetch_by_mp($email, $pid) {
309 2
        $q = $this->db->query("SELECT alert_id FROM alerts
310
            WHERE confirmed AND NOT deleted
311
            AND email = :email
312 2
            AND criteria = :criteria", array(
313 2
                ':email' => $email,
314
                ':criteria' => 'speaker:' . $pid
315 2
            ));
316 2
        if ($q->rows() > 0) {
317 1
            return true;
318
        } else {
319 1
            return false;
320
        }
321
    }
322
323 2
    public function email_exists($email) {
324
        // Returns true if there's a user with this email address.
325
326 2
        if ($email != "") {
327 2
            $q = $this->db->query("SELECT alert_id FROM alerts
328 2
                WHERE email = :email", array(
329
                    ':email' => $email
330 2
                ));
331 2
            if ($q->rows() > 0) {
332 1
                return true;
333
            } else {
334 1
                return false;
335
            }
336
        } else {
337
            return false;
338
        }
339
340
    }
341
342 12
    public function check_token($token) {
343 12
        if (!is_null($this->token_checked))
344 12
            return $this->token_checked;
345
346 12
        $arg = strstr($token, '::') ? '::' : '-';
347 12
        $token_parts = explode($arg, $token);
348 12
        if (count($token_parts) != 2)
349 12
            return false;
350
351 10
        list($alert_id, $registrationtoken) = $token_parts;
352 10
        if (!is_numeric($alert_id) || !$registrationtoken)
353 10
            return false;
354
355 10
        $q = $this->db->query("SELECT alert_id, email, criteria
356
                        FROM alerts
357
                        WHERE alert_id = :alert_id
358
                        AND registrationtoken = :registration_token
359 10
                        ", array(
360 10
                            ':alert_id' => $alert_id,
361
                            ':registration_token' => $registrationtoken
362 10
                        ));
363 10
        if (!$q->rows()) {
364 5
            $this->token_checked = false;
365 5
        } else {
366 5
            $this->token_checked = array(
367 5
                'id' => $q->field(0, 'alert_id'),
368 5
                'email' => $q->field(0, 'email'),
369 5
                'criteria' => $q->field(0, 'criteria'),
370
            );
371
        }
372
373 10
        return $this->token_checked;
374
    }
375
376
    public function fetch_by_token($confirmation) {
377
        $q = $this->db->query("SELECT alert_id, email, criteria
378
                        FROM alerts
379
                        WHERE registrationtoken = :registration_token
380
                        ", array(
381
                            ':registration_token' => $confirmation
382
                        )
383
                    );
384
385
        if (!$q->rows()) {
386
            return false;
387
        } else {
388
            return array(
389
                'id' => $q->field(0, 'alert_id'),
390
                'email' => $q->field(0, 'email'),
391
                'criteria' => $q->field(0, 'criteria'),
392
            );
393
        }
394
    }
395
396
    // The user has clicked the link in their confirmation email
397
    // and the confirm page has passed the token from the URL to here.
398
    // If all goes well the alert will be confirmed.
399
    // The alert will be active when scripts run each day to send the actual emails.
400 2
    public function confirm($token) {
401 2
        if (!($alert = $this->check_token($token))) return false;
402 1
        $this->criteria = $alert['criteria'];
403 1
        $this->email = $alert['email'];
404 1
        $r = $this->db->query("UPDATE alerts SET confirmed = 1, deleted = 0 WHERE alert_id = :alert_id", array(
405 1
            ':alert_id' => $alert['id']
406 1
            ));
407
408 1
        return $r->success();
409
    }
410
411
    // The user has clicked the link in their delete confirmation email
412
    // and the deletion page has passed the token from the URL to here.
413
    // If all goes well the alert will be deleted.
414 2
    public function delete($token) {
415 2
        if (!($alert = $this->check_token($token))) return false;
416 1
        $r = $this->db->query("DELETE FROM alerts WHERE alert_id = :alert_id", array(
417 1
            ':alert_id' => $alert['id']
418 1
            ));
419
420 1
        return $r->success();
421
    }
422
423 2
    public function suspend($token) {
424 2
        if (!($alert = $this->check_token($token))) return false;
425 1
        $r = $this->db->query("UPDATE alerts SET deleted = 2 WHERE alert_id = :alert_id", array(
426 1
            ':alert_id' => $alert['id']
427 1
            ));
428
429 1
        return $r->success();
430
    }
431
432 2
    public function resume($token) {
433 2
        if (!($alert = $this->check_token($token))) return false;
434 1
        $r = $this->db->query("UPDATE alerts SET deleted = 0 WHERE alert_id = :alert_id", array(
435 1
            ':alert_id' => $alert['id']
436 1
            ));
437
438 1
        return $r->success();
439
    }
440
441
    // Getters
442
    public function email() { return $this->email; }
443
    public function criteria() { return $this->criteria; }
444
    public function criteria_pretty($html = false) {
445
        $criteria = explode(' ',$this->criteria);
446
        $spokenby = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($this->criteria));
447
        $words = array();
448
        foreach ($criteria as $c) {
449
            if (!preg_match('#^speaker:(\d+)#',$c,$m)) {
450
                $words[] = $c;
451
            }
452
        }
453
        $criteria = '';
454
        if (count($words)) $criteria .= ($html?'<li>':'* ') . 'Mentions of [' . implode(' ', $words) . ']' . ($html?'</li>':'') . "\n";
455
        if ($spokenby) $criteria .= ($html?'<li>':'* ') . "Things by " . implode(' or ', $spokenby) . ($html?'</li>':'') . "\n";
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...
456
        return $criteria;
457
    }
458
459
}
460