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

Standard   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 363
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 363
rs 1.5789
wmc 84

13 Methods

Rating   Name   Duplication   Size   Complexity  
D addAlert() 0 43 10
A setUserData() 0 11 4
A display() 0 19 4
C formatSearchMemberData() 0 34 7
A formatSearchTerms() 0 6 2
C prettifyCriteria() 0 24 8
B checkForCommonMistakes() 0 22 6
B getBasicData() 0 26 5
C processAction() 0 35 11
B getUsersAlerts() 0 29 4
D searchForConstituenciesAndMembers() 0 35 10
A __construct() 0 3 1
D checkInput() 0 43 12

How to fix   Complexity   

Complex Class

Complex classes like Standard 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 Standard, and based on these observations, apply Extract Interface, too.

1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 12 and the first side effect is on line 5.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace MySociety\TheyWorkForYou\AlertView;
4
5
include_once '../../../www/includes/easyparliament/init.php';
6
include_once INCLUDESPATH . "easyparliament/people.php";
1 ignored issue
show
Bug introduced by
The constant MySociety\TheyWorkForYou\AlertView\INCLUDESPATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
7
include_once INCLUDESPATH . "easyparliament/member.php";
8
include_once INCLUDESPATH . "easyparliament/searchengine.php";
9
include_once INCLUDESPATH . '../../commonlib/phplib/auth.php';
10
include_once INCLUDESPATH . '../../commonlib/phplib/crosssell.php';
11
12
class Standard extends \MySociety\TheyWorkForYou\AlertView {
13
    public $data;
14
15
    public function __construct($THEUSER = NULL) {
16
        parent::__construct($THEUSER);
17
        $this->data = array();
18
    }
19
20
    public function display() {
21
        global $this_page;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
22
        $this_page = "alert";
23
24
        $this->processAction();
25
        $this->getBasicData();
26
        $this->checkInput();
27
        $this->searchForConstituenciesAndMembers();
28
29
        if (!sizeof($this->data['errors']) && ($this->data['keyword'] || $this->data['pid'])) {
30
            $this->addAlert();
31
        }
32
33
        $this->formatSearchTerms();
34
        $this->checkForCommonMistakes();
35
        $this->formatSearchMemberData();
36
        $this->setUserData();
37
38
        return $this->data;
39
    }
40
41
    private function processAction() {
42
        $token = get_http_var('t');
43
        $alert = $this->alert->check_token($token);
44
45
        $this->data['results'] = false;
46
        if ($action = get_http_var('action')) {
47
            $success = true;
48
            if ($action == 'Confirm') {
49
                $success = $this->confirmAlert($token);
50
                if ($success) {
51
                    $this->data['results'] = 'alert-confirmed';
52
                    $this->data['criteria'] = $this->prettifyCriteria($this->alert->criteria);
53
                }
54
            } elseif ($action == 'Suspend') {
55
                $success = $this->suspendAlert($token);
56
                if ($success) {
57
                    $this->data['results'] = 'alert-suspended';
58
                }
59
            } elseif ($action == 'Resume') {
60
                $success = $this->resumeAlert($token);
61
                if ($success) {
62
                    $this->data['results'] = 'alert-resumed';
63
                }
64
            } elseif ($action == 'Delete') {
65
                $success = $this->deleteAlert($token);
66
                if ($success) {
67
                    $this->data['results'] = 'alert-deleted';
68
                }
69
            }
70
            if (!$success) {
71
                $this->data['results'] = 'alert-fail';
72
            }
73
        }
74
75
        $this->data['alert'] = $alert;
76
    }
77
78
79
    private function getBasicData() {
80
        global $this_page;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
81
82
        if ($this->user->loggedin()) {
83
            $this->data['email'] = $this->user->email();
84
            $this->data['email_verified'] = true;
85
        } elseif ($this->data['alert']) {
86
            $this->data['email'] = $this->data['alert']['email'];
87
            $this->data['email_verified'] = true;
88
        } else {
89
            $this->data["email"] = trim(get_http_var("email"));
90
            $this->data['email_verified'] = false;
91
        }
92
        $this->data['keyword'] = trim(get_http_var("keyword"));
93
        $this->data['pid'] = trim(get_http_var("pid"));
94
        $this->data['alertsearch'] = trim(get_http_var("alertsearch"));
95
        $this->data['pc'] = get_http_var('pc');
96
        $this->data['submitted'] = get_http_var('submitted') || $this->data['pid'] || $this->data['keyword'];
97
        $this->data['token'] = get_http_var('t');
98
        $this->data['sign'] = get_http_var('sign');
99
        $this->data['site'] = get_http_var('site');
100
        $this->data['message'] = '';
101
102
        $ACTIONURL = new \MySociety\TheyWorkForYou\Url($this_page);
103
        $ACTIONURL->reset();
104
        $this->data['actionurl'] = $ACTIONURL->generate();
105
    }
106
107
    private function checkInput() {
108
        global $SEARCHENGINE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
109
110
        $errors = array();
111
112
        // Check each of the things the user has input.
113
        // If there is a problem with any of them, set an entry in the $errors array.
114
        // This will then be used to (a) indicate there were errors and (b) display
115
        // error messages when we show the form again.
116
117
        // Check email address is valid and unique.
118
        if (!$this->data['email']) {
119
            $errors["email"] = "Please enter your email address";
120
        } elseif (!validate_email($this->data["email"])) {
121
            // validate_email() is in includes/utilities.php
122
            $errors["email"] = "Please enter a valid email address";
123
        }
124
125
        if ($this->data['pid'] && !ctype_digit($this->data['pid'])) {
126
            $errors['pid'] = 'Invalid person ID passed';
127
        }
128
129
        $text = $this->data['alertsearch'];
130
        if (!$text) $text = $this->data['keyword'];
131
132
        if ($this->data['submitted'] && !$this->data['pid'] && !$text) {
133
            $errors['alertsearch'] = 'Please enter what you want to be alerted about';
134
        }
135
136
        if (strpos($text, '..')) {
137
            $errors['alertsearch'] = 'You probably don&rsquo;t want a date range as part of your criteria, as you won&rsquo;t be alerted to anything new!';
138
        }
139
140
        $se = new \SEARCHENGINE($text);
141
        if (!$se->valid) {
142
            $errors['alertsearch'] = 'That search appears to be invalid - ' . $se->error . ' - please check and try again.';
143
        }
144
145
        if (strlen($text) > 255) {
146
            $errors['alertsearch'] = 'That search is too long for our database; please split it up into multiple smaller alerts.';
147
        }
148
149
        $this->data['errors'] = $errors;
150
    }
151
152
    private function searchForConstituenciesAndMembers() {
153
        // Do the search
154
        if ($this->data['alertsearch']) {
155
            $this->data['members'] = \MySociety\TheyWorkForYou\Utility\Search::searchMemberDbLookupWithNames($this->data['alertsearch'], true);
156
            list ($this->data['constituencies'], $this->data['valid_postcode']) = \MySociety\TheyWorkForYou\Utility\Search::searchConstituenciesByQuery($this->data['alertsearch']);
157
        }
158
159
        # If the above search returned one result for member or constituency search,
160
        # use it immediately
161
162
        if (isset($this->data['members']) && $this->data['members']->rows() == 1) {
163
            $this->data['pid'] = $this->data['members']->field(0, 'person_id');
164
            unset($this->data['members']);
165
            $this->data['alertsearch'] = '';
166
        }
167
168
        if (isset($this->data['constituencies']) && count($this->data['constituencies']) == 1 && $this->data['valid_postcode']) {
169
            $MEMBER = new \MEMBER(array('constituency' => $this->data['constituencies'][0], 'house' => 1));
170
            $this->data['pid'] = $MEMBER->person_id();
171
            $this->data['pc'] = $this->data['alertsearch'];
172
            unset($this->data['constituencies']);
173
            $this->data['alertsearch'] = '';
174
        }
175
176
        if (isset($this->data['constituencies'])) {
177
            $cons = array();
178
            foreach ($this->data['constituencies'] as $constituency) {
179
                try {
180
                    $MEMBER = new \MEMBER(array('constituency'=>$constituency, 'house' => 1));
181
                    $cons[$constituency] = $MEMBER;
182
                } catch ( \MySociety\TheyWorkForYou\MemberException $e ) {
183
                    // do nothing
184
                }
185
            }
186
            $this->data['constituencies'] = $cons;
187
        }
188
    }
189
190
    private function addAlert() {
191
        $external_auth = auth_verify_with_shared_secret($this->data['email'], OPTION_AUTH_SHARED_SECRET, get_http_var('sign'));
1 ignored issue
show
Bug introduced by
The constant MySociety\TheyWorkForYou...TION_AUTH_SHARED_SECRET was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
192
        if ($external_auth) {
193
            $confirm = false;
194
        } elseif ($this->data['email_verified']) {
195
            $confirm = false;
196
        } else {
197
            $confirm = true;
198
        }
199
200
        // If this goes well, the alert will be added to the database and a confirmation email
201
        // will be sent to them.
202
        $success = $this->alert->add( $this->data, $confirm );
203
204
        if ($success>0 && !$confirm) {
205
            $result = 'alert-added';
206
        } elseif ($success>0) {
207
            $result = 'alert-confirmation';
208
        } elseif ($success == -2) {
209
            // we need to make sure we know that the person attempting to sign up
210
            // for the alert has that email address to stop people trying to work
211
            // out what alerts they are signed up to
212
            if ( $this->data['email_verified'] || ( $this->user->loggedin && $this->user->email() == $this->data['email'] ) ) {
213
                $result = 'alert-exists';
214
            } else {
215
                // don't throw an error message as that implies that they have already signed
216
                // up for the alert but instead pretend all is normal but send an email saying
217
                // that someone tried to sign them up for an existing alert
218
                $result = 'alert-already-signed';
219
                $this->alert->send_already_signedup_email($this->data);
220
            }
221
        } else {
222
            $result = 'alert-fail';
223
        }
224
225
        // don't need these anymore so get rid of them
226
        $this->data['keyword'] = '';
227
        $this->data['pid'] = '';
228
        $this->data['alertsearch'] = '';
229
        $this->data['pc'] = '';
230
231
        $this->data['results'] = $result;
232
        $this->data['criteria'] = $this->prettifyCriteria($this->alert->criteria);
233
    }
234
235
236
    private function formatSearchTerms() {
237
        if ( $this->data['alertsearch'] ) {
238
            $this->data['alertsearch_pretty'] = $this->prettifyCriteria($this->data['alertsearch']);
239
            $this->data['search_text'] = $this->data['alertsearch'];
240
        } else {
241
            $this->data['search_text'] = $this->data['keyword'];
242
        }
243
    }
244
245
    private function prettifyCriteria($alert_criteria) {
246
        $text = '';
247
        if ( $alert_criteria ) {
248
            $criteria = explode(' ', $alert_criteria);
249
            $words = array();
250
            $spokenby = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($alert_criteria));
251
252
            foreach ($criteria as $c) {
253
                if (!preg_match('#^speaker:(\d+)#',$c,$m)) {
254
                    $words[] = $c;
255
                }
256
            }
257
            if ( $spokenby && count($words) ) {
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...
258
                $text = implode(' or ', $spokenby) . ' mentions [' . implode(' ', $words) . ']';
259
            } else if ( count( $words ) ) {
260
                $text = '[' . implode(' ', $words) . ']' . ' is mentioned';
261
            } else 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...
262
                $text = implode(' or ', $spokenby) . " speaks";
263
            }
264
265
            return $text;
266
        }
267
268
        return $text;
269
    }
270
271
    private function checkForCommonMistakes() {
272
        $mistakes = array();
273
        if (strstr($this->data['alertsearch'], ',') > -1) {
274
            $mistakes['multiple'] = 1;
275
        }
276
277
        if (
278
                preg_match('#([A-Z]{1,2}\d+[A-Z]? ?\d[A-Z]{2})#i', $this->data['alertsearch'], $m) &&
279
                strlen($this->data['alertsearch']) > strlen($m[1]) &&
280
                validate_postcode($m[1])
281
        ) {
282
            $this->data['postcode'] = $m[1];
283
            $this->data['scottish_text'] = '';
284
            $this->data['mp_display_text'] = '';
285
            if (\MySociety\TheyWorkForYou\Utility\Postcode::postcodeIsScottish($m[1])) {
286
                $this->data['mp_display_text'] = 'your MP, ';
287
                $this->data['scottish_text'] = ' or MSP';
288
            }
289
            $mistakes['postcode_and'] = 1;
290
        }
291
292
        $this->data['mistakes'] = $mistakes;
293
    }
294
295
    private function formatSearchMemberData() {
296
        if ( isset($this->data['postcode']) ) {
297
            try {
298
                $postcode = $this->data['postcode'];
299
300
                $MEMBER = new \MEMBER( array('postcode' => $postcode) );
301
                // move the postcode to the front just to be tidy
302
                $tidy_alertsearch = $postcode . " " . trim(str_replace("$postcode", "", $this->data['alertsearch']));
303
                $alertsearch_display = str_replace("$postcode ", "", $tidy_alertsearch);
304
305
                $this->data['member_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch);
306
                $this->data['member_displaysearch'] = $alertsearch_display;
307
                $this->data['member'] = $MEMBER;
308
309
                if ( $this->data['scottish_text'] ) {
310
                    $constituencies = \MySociety\TheyWorkForYou\Utility\Postcode::postcodeToConstituencies($postcode);
311
                    if ( isset($constituencies['SPC']) ) {
312
                        $MEMBER = new \MEMBER(array('constituency' => $constituencies['SPC'], 'house' => 4));
313
                        $this->data['scottish_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch);
314
                        $this->data['scottish_member'] = $MEMBER;
315
                    }
316
                }
317
            } catch ( \MySociety\TheyWorkForYou\MemberException $e ) {
318
                $this->data['member_error'] = 1;
319
            }
320
        }
321
322
        if ( $this->data['pid'] ) {
323
            $MEMBER = new \MEMBER( array('person_id' => $this->data['pid']) );
324
            $this->data['pid_member'] = $MEMBER;
325
        }
326
327
        if ( $this->data['keyword'] ) {
328
            $this->data['display_keyword'] = $this->prettifyCriteria($this->data['keyword']);
329
        }
330
    }
331
332
    private function setUserData() {
333
        $this->data['current_mp'] = false;
334
        $this->data['alerts'] = array();
335
        if ($this->data['email_verified']) {
336
            if ($this->user->postcode()) {
337
                $current_mp = new \MEMBER(array('postcode' => $this->user->postcode()));
338
                if (!$this->alert->fetch_by_mp($this->user->email(), $current_mp->person_id())) {
339
                    $this->data['current_mp'] = $current_mp;
340
                }
341
            }
342
            $this->data['alerts'] = $this->getUsersAlerts();
343
        }
344
    }
345
346
    private function getUsersAlerts() {
347
        $q = $this->db->query('SELECT * FROM alerts WHERE email = :email
348
            AND deleted != 1 ORDER BY created', array(
349
                ':email' => $this->data['email']
350
            ));
351
352
        $alerts = array();
353
        $num_alerts = $q->rows();
354
        for ($i = 0; $i < $num_alerts; $i++) {
355
            $row = $q->row($i);
356
            $criteria = $this->prettifyCriteria($row['criteria']);
357
            $token = $row['alert_id'] . '-' . $row['registrationtoken'];
358
359
            $status = 'confirmed';
360
            if ( !$row['confirmed'] ) {
361
                $status = 'unconfirmed';
362
            } elseif ( $row['deleted'] == 2 ) {
363
                $status = 'suspended';
364
            }
365
366
            $alerts[] = array(
367
                'token' => $token,
368
                'status' => $status,
369
                'criteria' => $criteria,
370
                'raw' => $row['criteria']
371
            );
372
        }
373
374
        return $alerts;
375
    }
376
377
}
378