Passed
Pull Request — master (#1984)
by
unknown
05:40
created

Standard::processAction()   D

Complexity

Conditions 18
Paths 33

Size

Total Lines 59
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 46
nc 33
nop 0
dl 0
loc 59
rs 4.8666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
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
75
        // Determine if we should fetch alert counts
76
        // We skip counts when in form workflows (step, mp_step, disambiguation)
77
        $in_active_workflow = $this->data['step']           // Alert creation wizard steps
78
                             || $this->data['mp_step']         // MP alert creation
79
                             || $this->data['alertsearch']; // Search disambiguation (but not if showing results)
80
81
        $this->data['showing_main_alert_list'] = !$in_active_workflow;
82
83
        $this->setUserData();
84
85
        return $this->data;
86
    }
87
88
    # This only happens if we have an alert and want to do something to it.
89
    private function processAction() {
90
        $token = get_http_var('t');
91
        $alert = $this->alert->check_token($token);
92
93
        $this->data['results'] = false;
94
        if ($action = get_http_var('action')) {
95
            $success = true;
96
            if ($action == 'Confirm') {
97
                $success = $this->confirmAlert($token);
98
                if ($success) {
99
                    $this->data['results'] = 'alert-confirmed';
100
                    $this->data['criteria'] = $this->alert->criteria;
101
                    $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria, $this->alert->ignore_speaker_votes);
102
                }
103
            } elseif ($action == 'Suspend') {
104
                $success = $this->suspendAlert($token);
105
                if ($success) {
106
                    $this->data['results'] = 'alert-suspended';
107
                }
108
            } elseif ($action == 'Resume') {
109
                $success = $this->resumeAlert($token);
110
                if ($success) {
111
                    $this->data['results'] = 'alert-resumed';
112
                }
113
            } elseif ($action == 'Delete') {
114
                $success = $this->deleteAlert($token);
115
                if ($success) {
116
                    $this->data['results'] = 'alert-deleted';
117
                }
118
            } elseif ($action == 'Delete All') {
119
                $success = $this->deleteAllAlerts($token);
120
                if ($success) {
121
                    $this->data['results'] = 'all-alerts-deleted';
122
                }
123
            } elseif ($action == 'ignore-votes') {
124
                $success = $this->ignoreVotesAlert($token);
125
                if ($success) {
126
                    $this->data['criteria'] = $this->alert->criteria;
127
                    $display = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria, $this->alert->ignore_speaker_votes);
128
                    $this->data['display_criteria'] = str_replace('speaks', 'votes', $display);
129
                    $this->data['results'] = 'alert-ignore-votes';
130
                }
131
            } elseif ($action == 'include-votes') {
132
                $success = $this->includeVotesAlert($token);
133
                if ($success) {
134
                    $this->data['criteria'] = $this->alert->criteria;
135
                    $display = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria, $this->alert->ignore_speaker_votes);
136
                    $this->data['display_criteria'] = str_replace('speaks', 'votes', $display);
137
                    $this->data['results'] = 'alert-include-votes';
138
                }
139
            } elseif ($action == 'Abandon') {
140
                $this->data['results'] = 'changes-abandoned';
141
            }
142
            if (!$success) {
143
                $this->data['results'] = 'alert-fail';
144
            }
145
        }
146
147
        $this->data['alert'] = $alert;
148
    }
149
150
    # Process a screen in the alert creation wizard
151
    private function processStep() {
152
        # fetch a list of suggested terms. Need this for the define screen so we can filter out the suggested terms
153
        # and not show them if the user goes back
154
        if (in_array($this->data['step'], ['review', 'define', 'add_vector_related']) && !$this->data['shown_related']) {
155
            $suggestions = [];
156
            foreach ($this->data['keywords'] as $word) {
157
                $terms = $this->alert->get_related_terms($word);
158
                $terms = array_diff($terms, $this->data['keywords']);
159
                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...
160
                    $suggestions = array_merge($suggestions, $terms);
161
                }
162
            }
163
            if (count($suggestions) > 0) {
164
                $this->data['step'] = 'add_vector_related';
165
            }
166
            if ($this->data["step"] == 'add_vector_related') {
167
                $this->data['suggestions'] = $suggestions;
168
            }
169
            # confirm the alert. Handles both creating and editing alerts
170
        } elseif ($this->data['step'] == 'confirm') {
171
            $success = true;
172
            # if there's already an alert assume we are editing it and user must be logged in
173
            if ($this->data['alert']) {
174
                $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

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