Failed Conditions
Push — master ( df1eb1...82e0b5 )
by Struan
05:55
created

Standard   F

Complexity

Total Complexity 198

Size/Duplication

Total Lines 816
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 1
Metric Value
eloc 506
c 5
b 0
f 1
dl 0
loc 816
rs 2
wmc 198

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B processStep() 0 40 11
B addAlert() 0 48 10
F setUserData() 0 121 45
B display() 0 21 8
A getRecentResults() 0 34 4
A updateAlert() 0 3 1
B formatSearchMemberData() 0 38 8
A getSearchSections() 0 5 3
A formatSearchTerms() 0 8 3
A checkForCommonMistakes() 0 16 5
F getBasicData() 0 140 26
A wrap_phrase_in_quotes() 0 6 2
D processAction() 0 59 18
F checkInput() 0 54 16
F searchForConstituenciesAndMembers() 0 127 37

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
2
3
namespace MySociety\TheyWorkForYou\AlertView;
4
5
include_once '../../../www/includes/easyparliament/init.php';
6
include_once INCLUDESPATH . "easyparliament/member.php";
7
include_once INCLUDESPATH . "easyparliament/searchengine.php";
8
include_once INCLUDESPATH . '../../commonlib/phplib/auth.php';
9
include_once INCLUDESPATH . '../../commonlib/phplib/crosssell.php';
10
11
class Standard extends \MySociety\TheyWorkForYou\AlertView {
12
    public $data;
13
14
    public function __construct($THEUSER = null) {
15
        parent::__construct($THEUSER);
16
        $this->data = [];
17
    }
18
19
    /**
20
     * This file handles all actions on user alerts.
21
     *
22
     * It splits alerts into two types and they get different screens:
23
     *  * keyword alerts - a person wants an alert when a word or phrase is said
24
     *  * representative alerts - a person wants an alert when a representative speaks or votes
25
     *
26
     * There are several form variables that control what is displayed or what action is taken:
27
     * * action - this is used for things happening to alerts - creation/editing, deletion, suspension etc
28
     * * step - this determines which step of creating a keyword alert to display
29
     * * this_step - stores which step is currently being processed, also used for prev step button
30
     * * mp_step - show the representative alert screen
31
     * * results - stores the results of an action, used to display appropriate message
32
     *
33
     *  The steps in the keyword alert creation/editing process are:
34
     *  * define - Initial options where you can add a keyword, any exclusions plus select which chambers it applies to
35
     *  * add_vector_related - an optional related terms step which suggests extra terms if we find any in the database
36
     *  * review - a confirmation step
37
     *
38
     *  If the user is happy then they will click Confirm which will submit the form with action set to Confirm and the alert
39
     *  will be created or updated.
40
     *
41
     * If mp_step is one of the parameters set then the form to add a representative alert is displayed. This only has a creation
42
     * and a confirm step. As part of this the mp_search param will be submitted which will trigger checking detecting
43
     * which MP the user is trying to add. This will try the following steps:
44
     * * try to match the search text against a name
45
     * * check if the text contains a postcode and if so use that to look up the constituencies
46
     * * if there's not a postcode then try to match against a constituency name
47
     *
48
     * If there is more than one match then these are stored in the constituencies varaible and a choice is
49
     * presented to the user.
50
     *
51
     * The representive alert creation flow will also be entered if an alertsearch or pid parameter is submitted. This
52
     * is used for handling "create an alert when $rep speaks" links from elsewhere in the site (e.g. an MP page)
53
     *
54
     * Other actions such as suspend use a form with only an action value and the alert token if required.
55
     */
56
    public function display() {
57
        global $this_page;
58
        $this_page = "alert";
59
60
        $this->processAction();
61
        $this->getBasicData();
62
        $this->checkInput();
63
        $this->searchForConstituenciesAndMembers();
64
65
        if ($this->data['step'] || $this->data['addword']) {
66
            $this->processStep();
67
        } elseif (!$this->data['results'] == 'changes-abandoned' && !sizeof($this->data['errors']) && $this->data['submitted'] && ($this->data['keyword'] || $this->data['pid'])) {
68
            $this->addAlert();
69
        }
70
71
        $this->formatSearchTerms();
72
        $this->checkForCommonMistakes();
73
        $this->formatSearchMemberData();
74
        $this->setUserData();
75
76
        return $this->data;
77
    }
78
79
    # This only happens if we have an alert and want to do something to it.
80
    private function processAction() {
81
        $token = get_http_var('t');
82
        $alert = $this->alert->check_token($token);
83
84
        $this->data['results'] = false;
85
        if ($action = get_http_var('action')) {
86
            $success = true;
87
            if ($action == 'Confirm') {
88
                $success = $this->confirmAlert($token);
89
                if ($success) {
90
                    $this->data['results'] = 'alert-confirmed';
91
                    $this->data['criteria'] = $this->alert->criteria;
92
                    $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria, $this->alert->ignore_speaker_votes);
93
                }
94
            } elseif ($action == 'Suspend') {
95
                $success = $this->suspendAlert($token);
96
                if ($success) {
97
                    $this->data['results'] = 'alert-suspended';
98
                }
99
            } elseif ($action == 'Resume') {
100
                $success = $this->resumeAlert($token);
101
                if ($success) {
102
                    $this->data['results'] = 'alert-resumed';
103
                }
104
            } elseif ($action == 'Delete') {
105
                $success = $this->deleteAlert($token);
106
                if ($success) {
107
                    $this->data['results'] = 'alert-deleted';
108
                }
109
            } elseif ($action == 'Delete All') {
110
                $success = $this->deleteAllAlerts($token);
111
                if ($success) {
112
                    $this->data['results'] = 'all-alerts-deleted';
113
                }
114
            } elseif ($action == 'ignore-votes') {
115
                $success = $this->ignoreVotesAlert($token);
116
                if ($success) {
117
                    $this->data['criteria'] = $this->alert->criteria;
118
                    $display = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria, $this->alert->ignore_speaker_votes);
119
                    $this->data['display_criteria'] = str_replace('speaks', 'votes', $display);
120
                    $this->data['results'] = 'alert-ignore-votes';
121
                }
122
            } elseif ($action == 'include-votes') {
123
                $success = $this->includeVotesAlert($token);
124
                if ($success) {
125
                    $this->data['criteria'] = $this->alert->criteria;
126
                    $display = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria, $this->alert->ignore_speaker_votes);
127
                    $this->data['display_criteria'] = str_replace('speaks', 'votes', $display);
128
                    $this->data['results'] = 'alert-include-votes';
129
                }
130
            } elseif ($action == 'Abandon') {
131
                $this->data['results'] = 'changes-abandoned';
132
            }
133
            if (!$success) {
134
                $this->data['results'] = 'alert-fail';
135
            }
136
        }
137
138
        $this->data['alert'] = $alert;
139
    }
140
141
    # Process a screen in the alert creation wizard
142
    private function processStep() {
143
        # fetch a list of suggested terms. Need this for the define screen so we can filter out the suggested terms
144
        # and not show them if the user goes back
145
        if (($this->data['step'] == 'review' || $this->data['step'] == 'define') && !$this->data['shown_related']) {
146
            $suggestions = [];
147
            foreach ($this->data['keywords'] as $word) {
148
                $terms = $this->alert->get_related_terms($word);
149
                $terms = array_diff($terms, $this->data['keywords']);
150
                if ($terms && count($terms)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $terms 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...
151
                    $suggestions = array_merge($suggestions, $terms);
152
                }
153
            }
154
155
            if (count($suggestions) > 0) {
156
                $this->data['step'] = 'add_vector_related';
157
                $this->data['suggestions'] = $suggestions;
158
            }
159
            # confirm the alert. Handles both creating and editing alerts
160
        } elseif ($this->data['step'] == 'confirm') {
161
            $success = true;
162
            # if there's already an alert assume we are editing it and user must be logged in
163
            if ($this->data['alert']) {
164
                $success = $this->updateAlert($this->data['alert']['id'], $this->data);
0 ignored issues
show
Unused Code introduced by
The call to MySociety\TheyWorkForYou...Standard::updateAlert() has too many arguments starting with $this->data. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

164
                /** @scrutinizer ignore-call */ 
165
                $success = $this->updateAlert($this->data['alert']['id'], $this->data);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
165
                if ($success) {
166
                    # reset all the data to stop anything getting confused
167
                    $this->data['results'] = 'alert-confirmed';
168
                    $this->data['step'] = '';
169
                    $this->data['pid'] = '';
170
                    $this->data['alertsearch'] = '';
171
                    $this->data['pc'] = '';
172
                    $this->data['members'] = false;
173
                    $this->data['constituencies'] = [];
174
                    $this->data['member_constituencies'] = [];
175
                } else {
176
                    $this->data['results'] = 'alert-fail';
177
                    $this->data['step'] = 'review';
178
                }
179
            } else {
180
                $success = $this->addAlert();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $success is correct as $this->addAlert() targeting MySociety\TheyWorkForYou...ew\Standard::addAlert() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Unused Code introduced by
The assignment to $success is dead and can be removed.
Loading history...
181
                $this->data['step'] = '';
182
            }
183
        }
184
    }
185
186
187
    /*
188
     * set up the data we will be using in processing later steps
189
     */
190
    private function getBasicData() {
191
        global $this_page;
192
193
        if ($this->user->loggedin()) {
194
            $this->data['email'] = $this->user->email();
195
            $this->data['email_verified'] = true;
196
        } elseif ($this->data['alert']) {
197
            $this->data['email'] = $this->data['alert']['email'];
198
            $this->data['email_verified'] = true;
199
        } else {
200
            $this->data["email"] = trim(get_http_var("email"));
201
            $this->data['email_verified'] = false;
202
        }
203
204
        $this->data['token'] = get_http_var('t');
205
        $this->data['step'] = trim(get_http_var("step"));
206
        $this->data['mp_step'] = trim(get_http_var("mp_step"));
207
        $this->data['addword'] = trim(get_http_var("addword"));
208
        $this->data['this_step'] = trim(get_http_var("this_step"));
209
        $this->data['shown_related'] = get_http_var('shown_related');
210
        $this->data['match_all'] = get_http_var('match_all') == 'on';
211
        $this->data['keyword'] = trim(get_http_var("keyword"));
212
        $this->data['search_section'] = '';
213
        $this->data['alertsearch'] = trim(get_http_var("alertsearch"));
214
        $this->data['mp_search'] = trim(get_http_var("mp_search"));
215
        $this->data['pid'] = trim(get_http_var("pid"));
216
        $this->data['pc'] = get_http_var('pc');
217
        $this->data['submitted'] = get_http_var('submitted') || $this->data['pid'] || $this->data['keyword'] || $this->data['step'];
218
        $this->data['ignore_speaker_votes'] = get_http_var('ignore_speaker_votes');
219
220
        if ($this->data['addword'] || $this->data['step']) {
221
            $alert = $this->alert->check_token($this->data['token']);
222
223
            $criteria = '';
224
            $alert_ignore_speaker_votes = 0;
225
            if ($alert) {
226
                $criteria = $alert['criteria'];
227
                $alert_ignore_speaker_votes = $alert['ignore_speaker_votes'];
228
            }
229
230
            $ignore_speaker_votes = get_http_var('ignore_speaker_votes', $alert_ignore_speaker_votes);
231
            $this->data['ignore_speaker_votes'] = ($ignore_speaker_votes == 'on' || $ignore_speaker_votes == 1);
232
233
            $this->data['alert'] = $alert;
234
235
            $this->data['alert_parts'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($criteria, $alert_ignore_speaker_votes, true);
236
237
            $existing_rep = '';
238
            if (isset($this->data['alert_parts']['spokenby'])) {
239
                $existing_rep = $this->data['alert_parts']['spokenby'][0];
240
            }
241
242
            $existing_section = '';
243
            if (count($this->data['alert_parts']['sections'])) {
244
                $existing_section = $this->data['alert_parts']['sections'][0];
245
            }
246
247
            # only want to load this the first time, otherwise we want to default to the form
248
            # value
249
            if (!$this->data['this_step'] && $this->data['alert_parts']['match_all']) {
250
                $this->data['match_all'] = true;
251
            }
252
253
            $words = get_http_var('words', $this->data['alert_parts']['words'], true);
254
255
            $this->data['words'] = [];
256
            $this->data['keywords'] = [];
257
            foreach ($words as $word) {
258
                if (trim($word) != '') {
259
                    $this->data['keywords'][] = $word;
260
                    $this->data['words'][] = $this->wrap_phrase_in_quotes($word);
261
                }
262
            }
263
264
            $add_all_related = get_http_var('add_all_related');
265
            $this->data['add_all_related'] = $add_all_related;
266
            $this->data['skip_keyword_terms'] = [];
267
268
            $selected_related_terms = get_http_var('selected_related_terms', [], true);
269
            $this->data['selected_related_terms'] = $selected_related_terms;
270
271
            if ($this->data['step'] !== 'define') {
272
                if ($add_all_related) {
273
                    $this->data['selected_related_terms'] = [];
274
                    $related_terms = get_http_var('related_terms', [], true);
275
                    foreach ($related_terms as $term) {
276
                        $this->data['skip_keyword_terms'][] = $term;
277
                        $this->data['keywords'][] = $term;
278
                        $this->data['words'][] = $this->wrap_phrase_in_quotes($term);
279
                    }
280
                } else {
281
                    $this->data['skip_keyword_terms'] = $selected_related_terms;
282
                    foreach ($selected_related_terms as $term) {
283
                        $this->data['keywords'][] = $term;
284
                        $this->data['words'][] = $this->wrap_phrase_in_quotes($term);
285
                    }
286
                }
287
            }
288
            $this->data['exclusions'] = trim(get_http_var("exclusions", implode(' ', $this->data['alert_parts']['exclusions'])));
289
            $this->data['representative'] = trim(get_http_var("representative", $existing_rep));
290
291
            $this->data['search_section'] = trim(get_http_var("search_section", $existing_section));
292
293
            $separator = ' OR ';
294
            if ($this->data['match_all']) {
295
                $separator = ' ';
296
            }
297
            $this->data['keyword'] = implode($separator, $this->data['words']);
298
            if ($this->data['exclusions']) {
299
                $this->data['keyword'] = '(' . $this->data['keyword'] . ') -' . implode(' -', explode(' ', $this->data["exclusions"]));
300
            }
301
302
            $this->data['results'] = '';
303
304
            $this->getSearchSections();
305
        } elseif ($this->data['mp_step'] == 'mp_alert') {
306
            $alert = $this->alert->check_token($this->data['token']);
307
            if ($alert) {
308
                $ignore_speaker_votes = get_http_var('ignore_speaker_votes', $alert['ignore_speaker_votes']);
309
                $this->data['ignore_speaker_votes'] = ($ignore_speaker_votes == 'on' || $ignore_speaker_votes == 1);
310
311
                $existing_rep = '';
312
                if (isset($alert['criteria'])) {
313
                    $alert_parts = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($alert['criteria'], $alert['ignore_speaker_votes'], true);
314
                    $existing_rep = $alert_parts['spokenby'][0];
315
                    $this->data['pid'] = $alert_parts['pid'];
316
                }
317
                $this->data['keyword'] = get_http_var('mp_search', $existing_rep);
318
            } else {
319
                $this->data['ignore_speaker_votes'] = get_http_var('ignore_speaker_votes');
320
            }
321
        }
322
323
        $this->data['sign'] = get_http_var('sign');
324
        $this->data['site'] = get_http_var('site');
325
        $this->data['message'] = '';
326
327
        $ACTIONURL = new \MySociety\TheyWorkForYou\Url($this_page);
328
        $ACTIONURL->reset();
329
        $this->data['actionurl'] = $ACTIONURL->generate();
330
331
    }
332
333
    private function wrap_phrase_in_quotes($phrase) {
334
        if (strpos($phrase, ' ') > 0) {
335
            $phrase = '"' . trim($phrase, '"') . '"';
336
        }
337
338
        return $phrase;
339
    }
340
341
    /*
342
     * Check if the current alert has returned any results recently
343
     */
344
    private function getRecentResults($text) {
345
        global $SEARCHENGINE;
346
        $today = date_create();
347
        $one_week_ago = date_create('7 days ago');
348
        $restriction = date_format($one_week_ago, 'd/m/Y') . '..' . date_format($today, 'd/m/Y');
349
        $last_week_count = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $last_week_count is dead and can be removed.
Loading history...
350
        $se = new \SEARCHENGINE($text . ' ' . $restriction);
351
        $last_week_count = $se->run_count(0, 10);
352
        $se = new \SEARCHENGINE($text);
353
        $count = $se->run_count(0, 10, 'date');
354
        $last_mention = null;
355
        if ($count > 0) {
356
            $se->run_search(0, 1, 'date');
357
            $gid = $se->get_gids()[0];
358
359
            $q = ['gid_to' => $gid];
360
            do {
361
                $gid = $q['gid_to'];
362
                $q = $this->db->query("SELECT gid_to FROM gidredirect WHERE gid_from = :gid", [':gid' => $gid])->first();
363
            } while ($q);
364
365
            $q = $this->db->query("SELECT hansard.gid, hansard.hdate
366
            FROM hansard
367
            WHERE hansard.gid = :gid", [':gid' => $gid])->first();
368
369
            if ($q) {
370
                $last_mention_date = date_create($q['hdate']);
371
                $last_mention = date_format($last_mention_date, 'd M Y'); //$se->get_gids()[0];
372
            }
373
        }
374
        return [
375
            "last_mention" => $last_mention,
376
            "last_week_count" => $last_week_count,
377
            "all_time_count" => $count,
378
        ];
379
    }
380
381
    private function getSearchSections() {
382
        $this->data['sections'] = [];
383
        if ($this->data['search_section']) {
384
            foreach (explode(' ', $this->data['search_section']) as $section) {
385
                $this->data['sections'][] = \MySociety\TheyWorkForYou\Utility\Alert::sectionToTitle($section);
386
            }
387
        }
388
    }
389
390
    private function updateAlert($token) {
391
        $success = $this->alert->update($token, $this->data);
392
        return $success;
393
    }
394
395
    private function checkInput() {
396
        global $SEARCHENGINE;
397
398
        $errors = [];
399
400
        # these are the initial screens and so cannot have any errors as we've not submitted
401
        if (!$this->data['submitted'] || $this->data['step'] == 'define' || $this->data['mp_step'] == 'mp_alert') {
402
            $this->data['errors'] = $errors;
403
            return;
404
        }
405
406
        // Check each of the things the user has input.
407
        // If there is a problem with any of them, set an entry in the $errors array.
408
        // This will then be used to (a) indicate there were errors and (b) display
409
        // error messages when we show the form again.
410
411
        // Check email address is valid and unique.
412
        if (!$this->data['email']) {
413
            $errors["email"] = gettext("Please enter your email address");
414
        } elseif (!validate_email($this->data["email"])) {
415
            // validate_email() is in includes/utilities.php
416
            $errors["email"] = gettext("Please enter a valid email address");
417
        }
418
419
        if ($this->data['pid'] && !ctype_digit($this->data['pid'])) {
420
            $errors['pid'] = 'Invalid person ID passed';
421
        }
422
423
        $text = $this->data['alertsearch'];
424
        if ($this->data['mp_search']) {
425
            $text = $this->data['mp_search'];
426
        }
427
        if (!$text) {
428
            $text = $this->data['keyword'];
429
        }
430
431
        if ($this->data['submitted'] && !$this->data['pid'] && !$text) {
432
            $errors['alertsearch'] = gettext('Please enter what you want to be alerted about');
433
        }
434
435
        if (strpos($text, '..')) {
436
            $errors['alertsearch'] = gettext('You probably don&rsquo;t want a date range as part of your criteria, as you won&rsquo;t be alerted to anything new!');
437
        }
438
439
        $se = new \SEARCHENGINE($text);
440
        if (!$se->valid) {
441
            $errors['alertsearch'] = sprintf(gettext('That search appears to be invalid - %s - please check and try again.'), $se->error);
442
        }
443
444
        if (strlen($text) > 255) {
445
            $errors['alertsearch'] = gettext('That search is too long for our database; please split it up into multiple smaller alerts.');
446
        }
447
448
        $this->data['errors'] = $errors;
449
    }
450
451
452
    private function searchForConstituenciesAndMembers() {
453
        if ($this->data['results'] == 'changes-abandoned') {
454
            $this->data['members'] = false;
455
            return;
456
        }
457
458
        $cons_sort = function ($a, $b) {
459
            if ($a['rep_name'] == $b['rep_name']) {
460
                if ($a['constituency'] == $b['constituency']) {
461
                    if ($a['member']->family_name == $b['member']->family_name) {
462
                        return 0;
463
                    }
464
                    return ($a['member']->family_name < $b['member']->family_name) ? -1 : 1;
465
                }
466
                return ($a['constituency'] < $b['constituency']) ? -1 : 1;
467
            }
468
469
            return ($a['rep_name'] < $b['rep_name']) ? -1 : 1;
470
        };
471
472
        $text = $this->data['alertsearch'];
473
        if ($this->data['mp_search']) {
474
            $text = $this->data['mp_search'];
475
        }
476
        $errors = [];
477
        if ($text != '') {
478
            //$members_from_pids = array_values(\MySociety\TheyWorkForYou\Utility\Search::membersForIDs($this->data['alertsearch']));
479
            $members_from_names = [];
480
            $names_from_pids = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($text));
481
            foreach ($names_from_pids as $name) {
482
                $members_from_names = array_merge($members_from_names, \MySociety\TheyWorkForYou\Utility\Search::searchMemberDbLookupWithNames($name));
483
            }
484
            $members_from_words = \MySociety\TheyWorkForYou\Utility\Search::searchMemberDbLookupWithNames($text, true);
485
            $this->data['members'] = array_merge($members_from_words, $members_from_names);
486
            [$this->data['constituencies'], $this->data['valid_postcode']] = \MySociety\TheyWorkForYou\Utility\Search::searchConstituenciesByQuery($text, false);
487
            # if there's no postcode match then constituencies will only have a list of names of westminster constituencies
488
            # so do a lookup using the member table to get members in all constituency types
489
            if (!$this->data['valid_postcode']) {
490
                $this->data['member_constituencies'] = \MySociety\TheyWorkForYou\Utility\Search::searchMembersByConstituency($text);
491
            }
492
            $member_count = count($this->data['members']);
493
            $cons_count = count($this->data['constituencies']);
494
            $mem_cons_count = isset($this->data['member_constituencies']) ? count($this->data['member_constituencies']) : 0;
495
            if (!$this->data['alertsearch'] && $member_count == 0 && $cons_count == 0 && $mem_cons_count == 0) {
496
                $errors["representative"] = gettext("No matching representative found");
497
            }
498
        } elseif ($this->data['pid']) {
499
            $MEMBER = new \MySociety\TheyWorkForYou\Member(['person_id' => $this->data['pid']]);
500
            $this->data['members'] = [[
501
                "person_id" => $MEMBER->person_id,
502
                "given_name" => $MEMBER->given_name,
503
                "family_name" => $MEMBER->family_name,
504
                "house" => $MEMBER->house_disp,
505
                "title" => $MEMBER->title,
506
                "lordofname" => $MEMBER->lordofname,
507
                "constituency" => $MEMBER->constituency,
508
            ]];
509
        } elseif (isset($this->data['representative']) && $this->data['representative'] != '') {
510
            $this->data['members'] = \MySociety\TheyWorkForYou\Utility\Search::searchMemberDbLookupWithNames($this->data['representative'], true);
511
512
            $member_count = count($this->data['members']);
513
            if ($member_count == 0) {
514
                $errors["representative"] = gettext("No matching representative found");
515
            } elseif ($member_count > 1) {
516
                $errors["representative"] = gettext("Multiple matching representatives found, please select one.");
517
            } else {
518
                $this->data['pid'] = $this->data['members'][0]['person_id'];
519
            }
520
        } else {
521
            $this->data['members'] = [];
522
        }
523
524
525
        /*
526
         * If member_constituncies is set then it implies there wasn't a postcode match and hence we will have
527
         * a list of members which we can turn into constituencies, otherwise we have a hash of constituency names
528
         * keyed by mapit type
529
         */
530
        if (isset($this->data['member_constituencies'])) {
531
            $cons = [];
532
            foreach ($this->data['member_constituencies'] as $pid) {
533
                try {
534
                    $MEMBER = new \MySociety\TheyWorkForYou\Member(['person_id' => $pid]);
535
                    $cons[] = [ 'member' => $MEMBER, 'constituency' => $MEMBER->constituency, 'rep_name' => $MEMBER->getMostRecentMembership()['rep_name'] ];
536
                } catch (\MySociety\TheyWorkForYou\MemberException $e) {
537
                    // do nothing
538
                }
539
            }
540
            uasort($cons, $cons_sort);
541
            $this->data['constituencies'] = $cons;
542
        } elseif (isset($this->data['constituencies'])) {
543
            $cons = [];
544
            foreach ($this->data['constituencies'] as $type => $constituency) {
545
                try {
546
                    $house = \MySociety\TheyWorkForYou\Utility\Postcode::mapitTypeToHouse($type);
547
                    // regional list reps for wales and scotland
548
                    if ($type == 'SPE' || $type == 'WAE') {
549
                        $db = new \ParlDB();
550
                        $q = $db->query("SELECT person_id FROM member WHERE constituency = :constituency AND house = :house and left_reason = 'still_in_office'", [':constituency' => $constituency, ':house' => $house]);
551
                        foreach ($q as $row) {
552
                            $MEMBER = new \MySociety\TheyWorkForYou\Member(['person_id' => $row['person_id'], 'house' => $house]);
553
                            $cons[] = [ 'member' => $MEMBER, 'constituency' => $constituency, 'rep_name' => $MEMBER->getMostRecentMembership()['rep_name'] ];
554
                        }
555
556
                    } else {
557
                        $MEMBER = new \MySociety\TheyWorkForYou\Member(['constituency' => $constituency, 'house' => $house]);
558
                        $cons[] = [ 'member' => $MEMBER, 'constituency' => $constituency ];
559
                    }
560
                } catch (\MySociety\TheyWorkForYou\MemberException $e) {
561
                    // do nothing
562
                }
563
            }
564
            uasort($cons, $cons_sort);
565
            $this->data['constituencies'] = $cons;
566
        }
567
568
569
        if ($this->data['alertsearch'] && !$this->data['mp_step'] && ($this->data['pid'] || $this->data['members'] || $this->data['constituencies'])) {
570
            $this->data['mp_step'] = 'mp_alert';
571
            $this->data['mp_search'] = $this->data['alertsearch'];
572
            $this->data['alertsearch'] = '';
573
        }
574
575
        if (count($this->data["errors"]) > 0) {
576
            $this->data["errors"] = array_merge($this->data["errors"], $errors);
577
        } else {
578
            $this->data["errors"] = $errors;
579
        }
580
    }
581
582
    private function addAlert() {
583
        $external_auth = auth_verify_with_shared_secret($this->data['email'], OPTION_AUTH_SHARED_SECRET, get_http_var('sign'));
584
        if ($external_auth) {
585
            $confirm = false;
586
        } elseif ($this->data['email_verified']) {
587
            $confirm = false;
588
        } else {
589
            $confirm = true;
590
        }
591
592
        // If this goes well, the alert will be added to the database and a confirmation email
593
        // will be sent to them.
594
        $success = $this->alert->add($this->data, $confirm);
595
596
        if ($success > 0 && !$confirm) {
597
            $this->data['step'] = '';
598
            $this->data['mp_step'] = '';
599
            $result = 'alert-added';
600
        } elseif ($success > 0) {
601
            $this->data['step'] = '';
602
            $this->data['mp_step'] = '';
603
            $result = 'alert-confirmation';
604
        } elseif ($success == -2) {
605
            // we need to make sure we know that the person attempting to sign up
606
            // for the alert has that email address to stop people trying to work
607
            // out what alerts they are signed up to
608
            if ($this->data['email_verified'] || ($this->user->loggedin && $this->user->email() == $this->data['email'])) {
609
                $result = 'alert-exists';
610
            } else {
611
                // don't throw an error message as that implies that they have already signed
612
                // up for the alert but instead pretend all is normal but send an email saying
613
                // that someone tried to sign them up for an existing alert
614
                $result = 'alert-already-signed';
615
                $this->alert->send_already_signedup_email($this->data);
616
            }
617
        } else {
618
            $result = 'alert-fail';
619
        }
620
621
        // don't need these anymore so get rid of them
622
        $this->data['keyword'] = '';
623
        $this->data['pid'] = '';
624
        $this->data['alertsearch'] = '';
625
        $this->data['pc'] = '';
626
627
        $this->data['results'] = $result;
628
        $this->data['criteria'] = $this->alert->criteria;
629
        $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria, $this->alert->ignore_speaker_votes);
630
    }
631
632
633
    private function formatSearchTerms() {
634
        if ($this->data['alertsearch']) {
635
            $this->data['alertsearch_pretty'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->data['alertsearch']);
636
            $this->data['search_text'] = $this->data['alertsearch'];
637
        } elseif ($this->data['mp_search']) {
638
            $this->data['search_text'] = $this->data['mp_search'];
639
        } else {
640
            $this->data['search_text'] = $this->data['keyword'];
641
        }
642
    }
643
644
    private function checkForCommonMistakes() {
645
        $mistakes = [];
646
        if (strstr($this->data['alertsearch'], ',') > -1) {
647
            $mistakes['multiple'] = 1;
648
        }
649
650
        if (
651
            preg_match('#([A-Z]{1,2}\d+[A-Z]? ?\d[A-Z]{2})#i', $this->data['alertsearch'], $m) &&
652
            strlen($this->data['alertsearch']) > strlen($m[1]) &&
653
            validate_postcode($m[1])
654
        ) {
655
            $this->data['postcode'] = $m[1];
656
            $mistakes['postcode_and'] = 1;
657
        }
658
659
        $this->data['mistakes'] = $mistakes;
660
    }
661
662
    private function formatSearchMemberData() {
663
        if (isset($this->data['postcode'])) {
664
            try {
665
                $postcode = $this->data['postcode'];
666
667
                $MEMBER = new \MySociety\TheyWorkForYou\Member(['postcode' => $postcode]);
668
                // move the postcode to the front just to be tidy
669
                $tidy_alertsearch = $postcode . " " . trim(str_replace("$postcode", "", $this->data['alertsearch']));
670
                $alertsearch_display = str_replace("$postcode ", "", $tidy_alertsearch);
671
672
                $this->data['member_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch);
673
                $this->data['member_displaysearch'] = $alertsearch_display;
674
                $this->data['member'] = $MEMBER;
675
676
                if (isset($this->data['mistakes']['postcode_and'])) {
677
                    $constituencies = \MySociety\TheyWorkForYou\Utility\Postcode::postcodeToConstituencies($postcode);
678
                    if (isset($constituencies['SPC'])) {
679
                        $MEMBER = new \MySociety\TheyWorkForYou\Member(['constituency' => $constituencies['SPC'], 'house' => HOUSE_TYPE_SCOTLAND]);
680
                        $this->data['scottish_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch);
681
                        $this->data['scottish_member'] = $MEMBER;
682
                    } elseif (isset($constituencies['WAC'])) {
683
                        $MEMBER = new \MySociety\TheyWorkForYou\Member(['constituency' => $constituencies['WAC'], 'house' => HOUSE_TYPE_WALES]);
684
                        $this->data['welsh_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch);
685
                        $this->data['welsh_member'] = $MEMBER;
686
                    }
687
                }
688
            } catch (\MySociety\TheyWorkForYou\MemberException $e) {
689
                $this->data['member_error'] = 1;
690
            }
691
        }
692
693
        if ($this->data['pid']) {
694
            $MEMBER = new \MySociety\TheyWorkForYou\Member(['person_id' => $this->data['pid']]);
695
            $this->data['pid_member'] = $MEMBER;
696
        }
697
698
        if ($this->data['keyword']) {
699
            $this->data['display_keyword'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->data['keyword']);
700
        }
701
    }
702
703
    /*
704
     * This makes sure that the $this->data makes sense once all the other steps have been completed
705
     */
706
    private function setUserData() {
707
        if (!isset($this->data['criteria'])) {
708
            $criteria = $this->data['keyword'];
709
            if ($criteria) {
710
                if (!$this->data['match_all']) {
711
                    $has_or = strpos($criteria, ' OR ') !== false;
712
                    $missing_braces = strpos($criteria, '(') === false;
713
714
                    if ($has_or && $missing_braces) {
715
                        $criteria = "($criteria)";
716
                    }
717
                }
718
                if ($this->data['search_section']) {
719
                    $criteria .= " section:" . $this->data['search_section'];
720
                }
721
                if ($this->data['pid']) {
722
                    $criteria .= " speaker:" . $this->data['pid'];
723
                }
724
                $this->data['search_results']  = $this->getRecentResults($criteria);
725
            }
726
727
            $this->data['criteria'] = $criteria;
728
            $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($criteria);
729
        }
730
        if ($this->data['results'] == 'changes-abandoned') {
731
            $this->data['members'] = false;
732
            $this->data['alertsearch'] = '';
733
        }
734
735
        if ($this->data['alertsearch'] && !(isset($this->data['mistakes']['postcode_and']) || $this->data['members'] || $this->data['pid'] || $this->data['constituencies'])) {
736
            $this->data['step'] = 'define';
737
            $this->data['words'] = [$this->data['alertsearch']];
738
            $this->data['keywords'] = [$this->data['alertsearch']];
739
            $this->data['exclusions'] = '';
740
            $this->data['representative'] = '';
741
        } elseif ($this->data['alertsearch'] && ($this->data['members'] || $this->data['pid'])) {
742
            $this->data['mp_step'] = 'mp_alert';
743
            $this->data['mp_search'] = [$this->data['alertsearch']];
744
        } elseif ($this->data['members'] && $this->data['mp_step'] == 'mp_search' && count($this->data['errors']) == 0) {
745
            $this->data['mp_step'] = '';
746
        }
747
748
        $this->data['current_mp'] = false;
749
        $this->data['alerts'] = [];
750
        $this->data['keyword_alerts'] = [];
751
        $this->data['speaker_alerts'] = [];
752
        $this->data['spoken_alerts'] = [];
753
        $this->data['own_member_alerts'] = [];
754
        $this->data['all_keywords'] = [];
755
        $this->data['own_mp_criteria'] = '';
756
        $own_mp_criteria = '';
757
758
        if ($this->data['email_verified']) {
759
            if ($this->user->postcode()) {
760
                $current_mp = new \MySociety\TheyWorkForYou\Member(['postcode' => $this->user->postcode()]);
761
                if ($current_mp_alert = !$this->alert->fetch_by_mp($this->data['email'], $current_mp->person_id())) {
0 ignored issues
show
Unused Code introduced by
The assignment to $current_mp_alert is dead and can be removed.
Loading history...
762
                    $this->data['current_mp'] = $current_mp;
763
                    $own_mp_criteria = sprintf('speaker:%s', $current_mp->person_id());
0 ignored issues
show
Unused Code introduced by
The assignment to $own_mp_criteria is dead and can be removed.
Loading history...
764
                }
765
                $own_mp_criteria = $current_mp->full_name();
766
                $this->data['own_mp_criteria'] = $own_mp_criteria;
767
            }
768
            $this->data['alerts'] = \MySociety\TheyWorkForYou\Utility\Alert::forUser($this->data['email']);
769
            foreach ($this->data['alerts'] as $alert) {
770
                if (array_key_exists('spokenby', $alert) and sizeof($alert['spokenby']) == 1 and $alert['spokenby'][0] == $own_mp_criteria) {
771
                    $this->data['own_member_alerts'][] = $alert;
772
                } elseif (array_key_exists('spokenby', $alert)) {
773
                    if (!array_key_exists($alert['spokenby'][0], $this->data['spoken_alerts'])) {
774
                        $this->data['spoken_alerts'][$alert['spokenby'][0]] = [];
775
                    }
776
                    $this->data['spoken_alerts'][$alert['spokenby'][0]][] = $alert;
777
                }
778
            }
779
            foreach ($this->data['alerts'] as $alert) {
780
                $term = implode(' ', $alert['words']);
781
                $add = true;
782
                if (array_key_exists('spokenby', $alert)) {
783
                    $add = false;
784
                } elseif (array_key_exists($term, $this->data['spoken_alerts'])) {
785
                    $add = false;
786
                    $this->data['all_keywords'][] = $term;
787
                    $this->data['spoken_alerts'][$term][] = $alert;
788
                } elseif ($term == $own_mp_criteria) {
789
                    $add = false;
790
                    $this->data['all_keywords'][] = $term;
791
                    $this->data['own_member_alerts'][] = $alert;
792
                } elseif (\MySociety\TheyWorkForYou\Utility\Search::searchMemberDbLookupWithNames($term, true)) {
793
                    if (!array_key_exists($term, $this->data['spoken_alerts'])) {
794
                        $this->data['spoken_alerts'][$term] = [];
795
                    }
796
                    $add = false;
797
                    # need to add this to make it consistent so the front end know where to get the name
798
                    $alert['spokenby'] = [$term];
799
                    $this->data['all_keywords'][] = $term;
800
                    $this->data['spoken_alerts'][$term][] = $alert;
801
                }
802
                if ($add) {
803
                    $this->data['all_keywords'][] = $term;
804
                    $alert['search_results']  = $this->getRecentResults($alert["criteria"]);
805
                    $this->data['keyword_alerts'][] = $alert;
806
                }
807
            }
808
        } else {
809
            if ($this->data['alertsearch'] && $this->data['pc']) {
810
                $this->data['mp_step'] = 'mp_alert';
811
            }
812
        }
813
        if (count($this->data['alerts'])) {
814
            $this->data['delete_token'] = $this->data['alerts'][0]['token'];
815
        }
816
        if ($this->data['addword'] != '' || ($this->data['step'] && count($this->data['errors']) > 0)) {
817
            $this->data["step"] = get_http_var('this_step');
818
        } elseif ($this->data['mp_step'] && count($this->data['errors']) > 0) {
819
            $this->data["mp_step"] = 'mp_alert';
820
        } else {
821
            $this->data['this_step'] = '';
822
        }
823
824
        $this->data["search_term"] = $this->data['alertsearch'];
825
        if ($this->data['mp_search']) {
826
            $this->data["search_term"] = $this->data['mp_search'];
827
        }
828
    }
829
}
830