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

Member::cohortKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
ccs 3
cts 3
cp 1
crap 1
1
<?php
2
/**
3
 * Member Class
4
 *
5
 * @package TheyWorkForYou
6
 */
7
8
namespace MySociety\TheyWorkForYou;
9
10
/**
11
 * Member
12
 */
13
14
class Member extends \MEMBER {
15
16
    /**
17
     * Is Dead
18
     *
19
     * Determine if the member has died or not.
20
     *
21
     * @return boolean If the member is dead or not.
22
     */
23
24
    public function isDead() {
25
26
        $left_house = $this->left_house();
27
28
        if ($left_house) {
29
30
            // This member has left a house, and might be dead. See if they are.
31
32
            // Types of house to test for death.
33
            $house_types = array(
34
                HOUSE_TYPE_COMMONS,
35
                HOUSE_TYPE_LORDS,
36
                HOUSE_TYPE_SCOTLAND,
37
                HOUSE_TYPE_NI,
38
                HOUSE_TYPE_WALES,
39
                HOUSE_TYPE_LONDON_ASSEMBLY,
40
            );
41
42
            foreach ($house_types as $house_type) {
43
44
                if (in_array($house_type, $left_house) and
45
                    $left_house[$house_type]['reason'] and
46
                    $left_house[$house_type]['reason'] == 'Died'
47
                ) {
48
49
                    // This member has left a house because of death.
50
                    return true;
51
                }
52
53
            }
54
55
        }
56
57
        // If we get this far the member hasn't left a house due to death, and
58
        // is presumably alive.
59
        return false;
60
61
    }
62
63
 
64
    /**
65
     * Cohort Key
66
     *
67
     * Gets a key that defines the periods and party a member should be compared against
68
     *
69
     * @return string of party and entry dates
70
     */
71
72 13
    public function cohortKey($house = HOUSE_TYPE_COMMONS) {
0 ignored issues
show
Unused Code introduced by
The parameter $house is not used and could be removed. ( Ignorable by Annotation )

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

72
    public function cohortKey(/** @scrutinizer ignore-unused */ $house = HOUSE_TYPE_COMMONS) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
73
        // get the hash_id for the cohort this member belongs to
74 13
        $person_id = $this->person_id();
75 13
        return PartyCohort::getHashforPerson($person_id);
76
    }
77
78 20
    public function cohortPartyComparisonDirection() {
79
        // Is this MP and their cohort compared against the
80
        // first or last party they have?
81
        // By default, mirroring the partycohort query, 
82
        // this is the first party. 
83
        // However, this is ignored for the Speaker, and additional
84
        // individual overrides can be added below.
85
        // This makes most sense when a party switch was a long time ago.
86
        // As long as this person is in a cohort of 1 and has a unique set 
87
        // of memberships (which is likely for edge cases) it doesn't matter
88
        // that their original party is what is used when in the party cohort
89
        // construction query. 
90
91 20
        $person_id = $this->person_id();
92
93 20
        $direction = "first";
94 20
        if ($this->party() == "Speaker") {
95 20
            $direction = "last";
96
        }
97
98
        // MPs who have switched parties but should be compared against their
99
        // current party can go here.
100 20
        $use_last_party = array(10172, 14031, 25873);
101
102 20
        if (in_array($person_id, $use_last_party)) {
103 1
            $direction = "last";
104
        }
105
106 20
        return $direction;
107
    }
108
109
    public function currentPartyComparison(){
110
        # Simplify the current party when being compared to the original
111
        # Stops co-op and labour being seen as different
112
        $party = $this->party;
113
        if ( $party == 'Labour/Co-operative' ) {
114
            $party = 'Labour';
115
        }
116
        return $party;
117
    }
118
119 20
    public function cohortParty($house = HOUSE_TYPE_COMMONS){
120
        // The party being compared against for party comparison purposes
121
        // Unless specified by the condition in cohortPartyComparisonDirection
122
        // This is the first, not last, party a person has.
123
124 20
        $person_id = $this->person_id();
125 20
        $db = new \ParlDB;
126
127 20
        $cohort_direction = $this->cohortPartyComparisonDirection();
128
129 20
        if ($cohort_direction == "first") {
130 20
            $direction = " ASC";
131
        } else {
132 20
            $direction = " DESC";
133
        }
134
135 20
        $row = $db->query("SELECT party from member
136
        where house = :house
137
        and person_id = :person_id
138
        and party != ''
139
        order by entered_house
140 20
        " . $direction, array(":house" => $house,
141 20
                 ":person_id" => $person_id))->first();
142 20
        if ($row) {
143 20
            $party = $row["party"];
144 20
            if ( $party == 'Labour/Co-operative' ) {
145
                $party = 'Labour';
146
            }
147 20
            return $party;
148
        } else {
149
            return null;
150
        }
151
152
    }
153
154
    /*
155
     * Determine if the member is a new member of a house where new is
156
     * within the last 6 months.
157
     *
158
     * @param int house - identifier for the house, defaults to 1 for westminster
0 ignored issues
show
Bug introduced by
The type MySociety\TheyWorkForYou\house was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
159
     *
160
     * @return boolean
161
     */
162
163
164 1
    public function isNew($house = HOUSE_TYPE_COMMONS) {
165 1
        $date_entered = $this->getEntryDate($house);
166
167 1
        if ($date_entered) {
168 1
            $date_entered = new \DateTime($date_entered);
169 1
            $now = new \DateTime();
170
171 1
            $diff = $date_entered->diff($now);
172 1
            if ( $diff->y == 0 && $diff->m <= 6 ) {
173 1
                return true;
174
            }
175
        }
176
177 1
        return false;
178
    }
179
180
    /*
181
     * Get the date the person first entered a house. May not be for their current seat.
182
     *
183
     * @param int house - identifier for the house, defaults to 1 for westminster
184
     *
185
     * @return string - blank if no entry date for that house otherwise in YYYY-MM-DD format
186
     */
187
188 2
    public function getEntryDate($house = HOUSE_TYPE_COMMONS) {
189 2
        $date_entered = '';
190
191 2
        $entered_house = $this->entered_house($house);
192
193 2
        if ( $entered_house ) {
194 2
            $date_entered = $entered_house['date'];
195
        }
196
197 2
        return $date_entered;
198
    }
199
200
    /*
201
     * Get the date the person last left the house.
202
     *
203
     * @param int house - identifier for the house, defaults to 1 for westminster
204
     *
205
     * @return string - 9999-12-31 if they are still in that house otherwise in YYYY-MM-DD format
206
     */
207
208
    public function getLeftDate($house = HOUSE_TYPE_COMMONS) {
209
        $date_left = '';
210
211
        $left_house = $this->left_house($house);
212
213
        if ( $left_house ) {
214
            $date_left = $left_house['date'];
215
        }
216
217
        return $date_left;
218
    }
219
220
221
    public function getEUStance() {
222
        if (array_key_exists('eu_ref_stance', $this->extra_info())) {
223
            return $this->extra_info()['eu_ref_stance'];
224
        }
225
226
        return false;
227
    }
228
229
    /**
230
    * Image
231
    *
232
    * Return a URL for the member's image.
233
    *
234
    * @return string The URL of the member's image.
235
    */
236
237
    public function image() {
238
239
        $is_lord = $this->house(HOUSE_TYPE_LORDS);
240
        if ($is_lord) {
241
            list($image,$size) = Utility\Member::findMemberImage($this->person_id(), false, 'lord');
242
        } else {
243
            list($image,$size) = Utility\Member::findMemberImage($this->person_id(), false, true);
244
        }
245
246
        // We can determine if the image exists or not by testing if size is set
247
        if ($size !== null) {
248
            $exists = true;
249
        } else {
250
            $exists = false;
251
        }
252
253
        return array(
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('url' => $i...e, 'exists' => $exists) returns the type array<string,boolean|null|string> which is incompatible with the documented return type string.
Loading history...
254
            'url' => $image,
255
            'size' => $size,
256
            'exists' => $exists
257
        );
258
259
    }
260
261
    public function getMostRecentMembership() {
262
        $departures = $this->left_house();
263
264
        usort(
265
            $departures,
0 ignored issues
show
Bug introduced by
It seems like $departures can also be of type null; however, parameter $array of usort() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

265
            /** @scrutinizer ignore-type */ $departures,
Loading history...
266
            function ($a, $b) {
267
                if ( $a['date'] == $b['date'] ) {
268
                    return 0;
269
                } else if ( $a['date'] < $b['date'] ) {
270
                    return -1;
271
                } else {
272
                    return 1;
273
                }
274
            }
275
        );
276
277
        $latest_membership = array_slice($departures, -1)[0];
278
        $latest_membership['current'] = ($latest_membership['date'] == '9999-12-31');
279
        $latest_entrance = $this->entered_house($latest_membership['house']);
280
        $latest_membership['start_date'] = $latest_entrance['date'];
281
        $latest_membership['end_date'] = $latest_membership['date'];
282
        $latest_membership['rep_name'] = $this->getRepNameForHouse($latest_membership['house']);
283
284
        return $latest_membership;
285
    }
286
287
    /**
288
    * Offices
289
    *
290
    * Return an array of Office objects held (or previously held) by the member.
291
    *
292
    * @param string $include_only  Restrict the list to include only "previous" or "current" offices.
293
    * @param bool   $ignore_committees Ignore offices that appear to be committee memberships.
294
    *
295
    * @return array An array of Office objects.
296
    */
297
298
    public function offices($include_only = null, $ignore_committees = false) {
299
300
        $out = array();
301
302
        if (array_key_exists('office', $this->extra_info())) {
303
            $office = $this->extra_info();
304
            $office = $office['office'];
305
306
            foreach ($office as $row) {
307
                if ( $officeObject = $this->getOfficeObject($include_only, $ignore_committees, $row) ) {
308
                    $out[] = $officeObject;
309
                }
310
            }
311
        }
312
313
        return $out;
314
315
    }
316
317
    private function getOfficeObject($include_only, $ignore_committees, $row) {
318
        if (!$this->includeOffice($include_only, $row['to_date'])) {
319
            return null;
320
        }
321
        if ($ignore_committees && strpos($row['moffice_id'], 'Committee')) {
322
            return null;
323
        }
324
325
        $officeObject = new Office;
326
        $officeObject->title = prettify_office($row['position'], $row['dept']);
327
        $officeObject->from_date = $row['from_date'];
328
        $officeObject->to_date = $row['to_date'];
329
        $officeObject->source = $row['source'];
330
        return $officeObject;
331
    }
332
333
    private function includeOffice($include_only, $to_date) {
334
        $include_office = true;
335
336
        // If we should only include previous offices, and the to date is in the future, suppress this office.
337
        if ($include_only == 'previous' and $to_date == '9999-12-31') {
338
            $include_office = false;
339
        }
340
341
        // If we should only include previous offices, and the to date is in the past, suppress this office.
342
        if ($include_only == 'current' and $to_date != '9999-12-31') {
343
            $include_office = false;
344
        }
345
346
        return $include_office;
347
    }
348
349
    /**
350
    * Get Other Parties String
351
    *
352
    * Return a readable list of party changes for this member.
353
    *
354
    * @return string|null A readable list of the party changes for this member.
355
    */
356
357
    public function getOtherPartiesString() {
358
359
        if (!empty($this->other_parties) && $this->party != 'Speaker' && $this->party != 'Deputy Speaker') {
360
            $output = 'Party was ';
361
            $other_parties = array();
362
            foreach ($this->other_parties as $r) {
363
                $other_parties[] = $r['from'] . ' until ' . format_date($r['date'], SHORTDATEFORMAT);
364
            }
365
            $output .= join('; ', $other_parties);
366
            return $output;
367
        } else {
368
            return null;
369
        }
370
371
    }
372
373
    /**
374
    * Get Other Constituencies String
375
    *
376
    * Return a readable list of other constituencies for this member.
377
    *
378
    * @return string|null A readable list of the other constituencies for this member.
379
    */
380
381
    public function getOtherConstituenciesString() {
382
383
        if ($this->other_constituencies) {
384
            return 'Also represented ' . join('; ', array_keys($this->other_constituencies));
385
        } else {
386
            return null;
387
        }
388
389
    }
390
391
    /**
392
    * Get Entered/Left Strings
393
    *
394
    * Return an array of readable strings covering when people entered or left
395
    * various houses. Returns an array since it's possible for a member to have
396
    * done several of these things.
397
    *
398
    * @return array An array of strings of when this member entered or left houses.
399
    */
400 1
    public function getEnterLeaveStrings() {
401 1
        $output = array();
402
403 1
        $output[] = $this->entered_house_line(HOUSE_TYPE_LORDS, gettext('House of Lords'));
404
405 1
        if (isset($this->left_house[HOUSE_TYPE_COMMONS]) && isset($this->entered_house[HOUSE_TYPE_LORDS])) {
406
            $string = '<strong>';
407
            $string .= sprintf(gettext('Previously MP for %s until %s'), $this->left_house[HOUSE_TYPE_COMMONS]['constituency'], $this->left_house[HOUSE_TYPE_COMMONS]['date_pretty']);
408
            $string .= '</strong>';
409
            if ($this->left_house[HOUSE_TYPE_COMMONS]['reason']) {
410
                $string .= ' &mdash; ' . $this->left_house[HOUSE_TYPE_COMMONS]['reason'];
411
            }
412
            $output[] = $string;
413
        }
414
415 1
        $output[] = $this->left_house_line(HOUSE_TYPE_LORDS, gettext('House of Lords'));
416
417 1
        if (isset($this->extra_info['lordbio'])) {
418
            $output[] = '<strong>Positions held at time of appointment:</strong> ' . $this->extra_info['lordbio'] .
419
                ' <small>(from <a href="' .
420
                $this->extra_info['lordbio_from'] . '">Number 10 press release</a>)</small>';
421
        }
422
423 1
        $output[] = $this->entered_house_line(HOUSE_TYPE_COMMONS, gettext('House of Commons'));
424
425
        # If they became a Lord, we handled this above.
426 1
        if ($this->house(HOUSE_TYPE_COMMONS) && !$this->current_member(HOUSE_TYPE_COMMONS) && !$this->house(HOUSE_TYPE_LORDS)) {
427
            $output[] = $this->left_house_line(HOUSE_TYPE_COMMONS, gettext('House of Commons'));
428
        }
429
430 1
        $output[] = $this->entered_house_line(HOUSE_TYPE_NI, gettext('Assembly'));
431 1
        $output[] = $this->left_house_line(HOUSE_TYPE_NI, gettext('Assembly'));
432 1
        $output[] = $this->entered_house_line(HOUSE_TYPE_SCOTLAND, gettext('Scottish Parliament'));
433 1
        $output[] = $this->left_house_line(HOUSE_TYPE_SCOTLAND, gettext('Scottish Parliament'));
434 1
        $output[] = $this->entered_house_line(HOUSE_TYPE_WALES, gettext('Welsh Parliament'));
435 1
        $output[] = $this->left_house_line(HOUSE_TYPE_WALES, gettext('Welsh Parliament'));
436 1
        $output[] = $this->entered_house_line(HOUSE_TYPE_LONDON_ASSEMBLY, gettext('London Assembly'));
437 1
        $output[] = $this->left_house_line(HOUSE_TYPE_LONDON_ASSEMBLY, gettext('London Assembly'));
438
439 1
        $output = array_values(array_filter($output));
440 1
        return $output;
441
    }
442
443 1
    private function entered_house_line($house, $house_name) {
444 1
        if (isset($this->entered_house[$house]['date'])) {
445 1
            $string = "<strong>";
446 1
            if (strlen($this->entered_house[$house]['date_pretty'])==4) {
447
                $string .= sprintf(gettext("Entered the %s in %s"), $house_name, $this->entered_house[$house]['date_pretty']);
448
            } else {
449 1
                $string .= sprintf(gettext("Entered the %s on %s"), $house_name, $this->entered_house[$house]['date_pretty']);
450
            }
451 1
            $string .= '</strong>';
452 1
            if ($this->entered_house[$house]['reason']) {
453 1
                $string .= ' &mdash; ' . $this->entered_house[$house]['reason'];
454
            }
455 1
            return $string;
456
        }
457 1
    }
458
459 1
    private function left_house_line($house, $house_name) {
460 1
        if ($this->house($house) && !$this->current_member($house)) {
461 1
            $string = "<strong>";
462 1
            if (strlen($this->left_house[$house]['date_pretty'])==4) {
463
                $string .= sprintf(gettext("Left the %s in %s"), $house_name, $this->left_house[$house]['date_pretty']);
464
            } else {
465 1
                $string .= sprintf(gettext("Left the %s on %s"), $house_name, $this->left_house[$house]['date_pretty']);
466
            }
467 1
            $string .= '</strong>';
468 1
            if ($this->left_house[$house]['reason']) {
469 1
                $string .= ' &mdash; ' . $this->left_house[$house]['reason'];
470
            }
471 1
            return $string;
472
        }
473 1
    }
474
475
    public function getPartyPolicyDiffs($partyCohort, $policiesList, $positions, $only_diffs = false) {
476
        $policy_diffs = array();
477
        $party_positions = $partyCohort->getAllPolicyPositions($policiesList);
478
479
        if ( !$party_positions ) {
480
            return $policy_diffs;
481
        }
482
483
        foreach ( $positions->positionsById as $policy_id => $details ) {
484
            if ( $details['has_strong'] && $details['score'] != -1 && isset($party_positions[$policy_id])) {
485
                $mp_score = $details['score'];
486
                $party_position = $party_positions[$policy_id];
487
                $party_score = $party_position['score'];
488
                $year_min = substr($party_position['date_min'], 0, 4);
489
                $year_max = substr($party_position['date_max'], 0, 4);
490
                if ($year_min == $year_max) {
491
                    $party_voting_summary = sprintf("%d votes, in %d", $party_position['divisions'], $year_min);
492
                } else {
493
                    $party_voting_summary = sprintf("%d votes, between %d–%d", $party_position['divisions'], $year_min, $year_max);
494
                }
495
496
                $score_diff = $this->calculatePolicyDiffScore($mp_score, $party_score);
497
498
                // skip anything that isn't a yes vs no diff
499
                if ( $only_diffs && $score_diff < 2 ) {
500
                    continue;
501
                }
502
                $policy_diffs[$policy_id] = [
503
                    'policy_text' => $details['policy'],
504
                    'score_difference' => $score_diff,
505
                    'person_position' => $details['position'],
506
                    'summary' => $details['summary'],
507
                    'party_position' => $party_position['position'],
508
                    'party_voting_summary' => $party_voting_summary,
509
                ];
510
            }
511
        }
512
513
        uasort($policy_diffs, function($a, $b) {
514
            return $b['score_difference'] - $a['score_difference'];
515
        });
516
517
        return $policy_diffs;
518
    }
519
520
    private function calculatePolicyDiffScore( $mp_score, $party_score ) {
521
        $score_diff = abs($mp_score - $party_score);
522
        // if they are on opposite sides of mixture of for and against
523
        if (
524
            ( $mp_score < 0.4 && $party_score > 0.6 ) ||
525
            ( $mp_score > 0.6 && $party_score < 0.4 )
526
        ) {
527
            $score_diff += 2;
528
        // if on is mixture of for and against and one is for/against
529
        } else if (
530
            ( $mp_score > 0.4 && $mp_score < 0.6 && ( $party_score > 0.6 || $party_score < 0.4 ) ) ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($mp_score > 0.4 && $mp_... 0.6 || $mp_score < 0.4, Probably Intended Meaning: $mp_score > 0.4 && $mp_s...0.6 || $mp_score < 0.4)
Loading history...
531
            ( $party_score > 0.4 && $party_score < 0.6 && ( $mp_score > 0.6 || $mp_score < 0.4 ) )
532
        ) {
533
            $score_diff += 1;
534
        }
535
536
        return $score_diff;
537
    }
538
539 1
    public static function getRegionalList($postcode, $house, $type) {
540 1
        $db = new \ParlDB;
541
542 1
        $mreg = array();
543 1
        $constituencies = \MySociety\TheyWorkForYou\Utility\Postcode::postcodeToConstituencies($postcode);
544 1
        if ( isset($constituencies[$type]) ) {
545 1
            $cons_name = $constituencies[$type];
546 1
            $query_base = "SELECT member.person_id, title, lordofname, given_name, family_name, constituency, house
547
                FROM member, person_names
548
                WHERE
549
                member.person_id = person_names.person_id
550
                AND person_names.type = 'name'
551
                AND constituency = :cons_name
552
                AND house = :house
553
                AND left_house >= start_date
554
                AND left_house <= end_date";
555 1
            $q = $db->query("$query_base AND left_reason = 'still_in_office'",
556
                array(
557 1
                    ':house' => $house,
558 1
                    ':cons_name' => $cons_name
559
                )
560
            );
561 1
            if ( !$q->rows() && ($dissolution = Dissolution::db()) ) {
562
                $q = $db->query("$query_base AND $dissolution[query]",
563
                    array(
564
                        ':house' => $house,
565
                        ':cons_name' => $cons_name,
566
                    ) + $dissolution['params']
567
                );
568
            }
569
570 1
            foreach ($q as $row) {
571 1
                $name = member_full_name($house, $row['title'], $row['given_name'], $row['family_name'], $row['lordofname']);
572 1
                $mreg[] = array(
573 1
                    'person_id' => $row['person_id'],
574 1
                    'name' => $name,
575 1
                    'house' => $row['house'],
576 1
                    'constituency' => gettext($row['constituency'])
577
                );
578
            }
579
        }
580
581 1
        return $mreg;
582
    }
583
584
    public static function getRepNameForHouse($house) {
585
        switch ( $house ) {
586
            case HOUSE_TYPE_COMMONS:
587
                $name = 'MP';
588
                break;
589
            case HOUSE_TYPE_LORDS:
590
                $name = 'Peer';
591
                break;
592
            case HOUSE_TYPE_NI:
593
                $name = 'MLA';
594
                break;
595
            case HOUSE_TYPE_SCOTLAND:
596
                $name = 'MSP';
597
                break;
598
            case HOUSE_TYPE_WALES:
599
                $name = 'MS';
600
                break;
601
            case HOUSE_TYPE_LONDON_ASSEMBLY:
602
                $name = 'London Assembly Member';
603
                break;
604
            case HOUSE_TYPE_ROYAL:
605
                $name = 'Member of royalty';
606
                break;
607
            default:
608
                $name = '';
609
        }
610
        return $name;
611
    }
612
613
}
614