Failed Conditions
Branch master (215e8c)
by Johannes
04:26
created

Divisions::getRecentDivisionsForPolicies()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 15
nc 4
nop 2
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * Policy Positions
4
 *
5
 * @package TheyWorkForYou
6
 */
7
8
namespace MySociety\TheyWorkForYou;
9
10
class Divisions {
11
12
    /**
13
     * Member
14
     */
15
16
    private $member;
17
18
    /**
19
     * DB handle
20
     */
21
    private $db;
22
23
    private $positions;
24
    private $policies;
25
26
    /**
27
     * Constructor
28
     *
29
     * @param Member   $member   The member to get positions for.
30
     */
31
32
    public function __construct(Member $member = NULL, PolicyPositions $positions = NULL, Policies $policies = NULL)
33
    {
34
        $this->member = $member;
35
        $this->positions = $positions;
36
        $this->policies = $policies;
37
        $this->db = new \ParlDB;
38
    }
39
40
    public static function getMostRecentDivisionDate() {
41
      $db = new \ParlDB;
42
      $q = $db->query(
43
        "SELECT policy_id, max(division_date) as recent from policydivisions GROUP BY policy_id"
44
      );
45
46
      $policy_maxes = array();
47
      $row_count = $q->rows();
48
      for ($n = 0; $n < $row_count; $n++) {
49
        $policy_maxes[$q->field($n, 'policy_id')] = $q->field( $n, 'recent' );
50
      }
51
      $policy_maxes['latest'] = max(array_values($policy_maxes));
52
      return $policy_maxes;
53
    }
54
55
    public function getRecentDivisions($number = 20) {
56
        // Grab distinct divisions as sometimes we have the same division for more than one policy
57
        // and we don't want to display it twice
58
        $q = $this->db->query(
59
          "SELECT distinct division_id, division_title, yes_text, no_text, division_date, division_number, gid, direction, majority_vote,
60
          yes_total, no_total, absent_total, both_total
61
          FROM policydivisions ORDER BY division_date DESC, division_number DESC LIMIT :count",
62
            array(
63
                ':count' => $number
64
            )
65
        );
66
67
        $divisions = array();
68
        foreach ($q->data as $division) {
69
            $divisions[] = $this->getParliamentDivisionDetails($division);
70
        }
71
72
        return array('divisions' => $divisions);
73
    }
74
75
    public function getRecentDivisionsForPolicies($policies, $number = 20) {
76
        $args = array(':number' => $number);
77
78
        $quoted = array();
79
        foreach ($policies as $policy) {
80
            $quoted[] = $this->db->quote($policy);
81
        }
82
        $policies_str = implode(',', $quoted);
83
84
        $q = $this->db->query(
85
            "SELECT division_id, division_title, yes_text, no_text, division_date, division_number, gid, direction, majority_vote,
86
            yes_total, no_total, absent_total, both_total
87
            FROM policydivisions
88
            WHERE policy_id in ($policies_str)
89
            GROUP BY division_id
90
            ORDER by division_date DESC LIMIT :number",
91
            $args
92
        );
93
94
        $divisions = array();
95
        $row_count = $q->rows();
96
        for ($n = 0; $n < $row_count; $n++) {
97
          $divisions[] = $this->getParliamentDivisionDetails($q->row($n));
98
        }
99
100
        return $divisions;
101
    }
102
103
    /**
104
     *
105
     * Get a list of division votes related to a policy
106
     *
107
     * Returns an array with one key ( the policyID ) containing a hash
108
     * with a policy_id key and a divisions key which contains an array
109
     * with details of all the divisions.
110
     *
111
     * Each division is a hash with the following fields:
112
     *    division_id, date, vote, gid, url, text, strong
113
     *
114
     * @param $policyId The ID of the policy to get divisions for
115
     */
116
117
    public function getMemberDivisionsForPolicy($policyID = null) {
118
        $where_extra = '';
119
        $args = array(':person_id' => $this->member->person_id);
120
        if ( $policyID ) {
121
            $where_extra = 'AND policy_id = :policy_id';
122
            $args[':policy_id'] = $policyID;
123
        }
124
        $q = $this->db->query(
125
            "SELECT policy_id, division_id, division_title, yes_text, no_text, division_date, division_number, vote, gid, direction
126
            FROM policydivisions JOIN persondivisionvotes USING(division_id)
127
            WHERE person_id = :person_id AND direction <> 'abstention' $where_extra
128
            ORDER by policy_id, division_date",
129
            $args
130
        );
131
132
        return $this->divisionsByPolicy($q);
133
    }
134
135
    public function getMemberDivisionDetails() {
136
        $args = array(':person_id' => $this->member->person_id);
137
138
        $policy_divisions = array();
139
140
        $q = $this->db->query(
141
            "SELECT policy_id, policy_vote, vote, count(division_id) as total,
142
            max(year(division_date)) as latest, min(year(division_date)) as earliest
143
            FROM policydivisions JOIN persondivisionvotes USING(division_id)
144
            WHERE person_id = :person_id AND direction <> 'abstention'
145
            GROUP BY policy_id, policy_vote, vote",
146
            $args
147
        );
148
149
        $row_count = $q->rows();
150
        for ($n = 0; $n < $row_count; $n++) {
151
          $policy_id = $q->field($n, 'policy_id');
152
153
          if (!array_key_exists($policy_id, $policy_divisions)) {
154
            $summary = array(
155
              'max' => $q->field($n, 'latest'),
156
              'min' => $q->field($n, 'earliest'),
157
              'total' => $q->field($n, 'total'),
158
              'for' => 0, 'against' => 0, 'absent' => 0, 'both' => 0, 'tell' => 0
159
            );
160
161
            $policy_divisions[$policy_id] = $summary;
162
          }
163
164
          $summary = $policy_divisions[$policy_id];
165
166
          $summary['total'] += $q->field($n, 'total');
167 View Code Duplication
          if ($summary['max'] < $q->field($n, 'latest')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
168
              $summary['max'] = $q->field($n, 'latest');
169
          }
170 View Code Duplication
          if ($summary['min'] > $q->field($n, 'latest')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
              $summary['min'] = $q->field($n, 'latest');
172
          }
173
174
          $vote = $q->field($n, 'vote');
175
          $policy_vote = str_replace('3', '', $q->field($n, 'policy_vote'));
176
          if ( $vote == 'absent' ) {
177
              $summary['absent'] += $q->field($n, 'total');
178
          } else if ( $vote == 'both' ) {
179
              $summary['both'] += $q->field($n, 'total');
180
          } else if ( strpos($vote, 'tell') !== FALSE ) {
181
              $summary['tell'] += $q->field($n, 'total');
182
          } else if ( $policy_vote == $vote ) {
183
              $summary['for'] += $q->field($n, 'total');
184
          } else if ( $policy_vote != $vote ) {
185
              $summary['against'] += $q->field($n, 'total');
186
          }
187
188
          $policy_divisions[$policy_id] = $summary;
189
        }
190
191
        return $policy_divisions;
192
    }
193
194
    public function getDivisionResults($division_id) {
195
        $args = array(
196
            ':division_id' => $division_id
197
        );
198
        $q = $this->db->query(
199
            "SELECT division_id, division_title, yes_text, no_text, division_date, division_number, gid, direction,
200
            yes_total, no_total, absent_total, both_total, majority_vote
201
            FROM policydivisions
202
            WHERE division_id = :division_id",
203
            $args
204
        );
205
206
        if ($q->rows == 0) {
207
            return false;
208
        }
209
210
        $details = $this->getParliamentDivisionDetails($q->row(0));
211
        $details['division_title'] = $q->row(0)['division_title'];
212
213
        $q = $this->db->query(
214
            "SELECT person_id, vote, given_name, family_name
215
            FROM persondivisionvotes JOIN person_names USING(person_id)
216
            WHERE division_id = :division_id
217
            ORDER by family_name",
218
            $args
219
        );
220
221
        $votes = array(
222
          'yes_votes' => array(),
223
          'no_votes' => array(),
224
          'absent_votes' => array(),
225
          'both_votes' => array()
226
        );
227
228
        foreach ($q->data as $vote) {
229
            $detail = array(
230
              'person_id' => $vote['person_id'],
231
              'name' => $vote['given_name'] . ' ' . $vote['family_name'],
232
              'teller' => false
233
            );
234
235
            if (strpos($vote['vote'], 'tell') !== FALSE) {
236
                $detail['teller'] = true;
237
            }
238
239
            if ($vote['vote'] == 'aye' or $vote['vote'] == 'tellaye') {
240
              $votes['yes_votes'][] = $detail;
241
            } else if ($vote['vote'] == 'no' or $vote['vote'] == 'tellno') {
242
              $votes['no_votes'][] = $detail;
243
            } else if ($vote['vote'] == 'absent') {
244
              $votes['absent_votes'][] = $detail;
245
            } else if ($vote['vote'] == 'both') {
246
              $votes['both_votes'][] = $detail;
247
            }
248
249
        }
250
251
        $details = array_merge($details, $votes);
252
253
        return $details;
254
    }
255
256
    public function getDivisionResultsForMember($division_id, $person_id) {
257
        $args = array(
258
            ':division_id' => $division_id,
259
            ':person_id' => $person_id
260
        );
261
        $q = $this->db->query(
262
            "SELECT division_id, division_title, yes_text, no_text, division_date, division_number, gid, direction, vote
263
            FROM policydivisions JOIN persondivisionvotes USING(division_id)
264
            WHERE division_id = :division_id AND person_id = :person_id",
265
            $args
266
        );
267
268
        // if the vote was before or after the MP was in Parliament
269
        // then there won't be a row
270
        if ($q->rows == 0) {
271
            return false;
272
        }
273
274
        $details = $this->getDivisionDetails($q->row(0));
275
        return $details;
276
    }
277
278
    public function generateSummary($votes) {
279
        $max = $votes['max'];
280
        $min = $votes['min'];
281
282
        $actions = array(
283
            $votes['for'] . ' ' . make_plural('vote', $votes['for']) . ' for',
284
            $votes['against'] . ' ' . make_plural('vote', $votes['against']) . ' against'
285
        );
286
287
        if ( $votes['both'] ) {
288
            $actions[] = $votes['both'] . ' ' . make_plural('abstention', $votes['both']);
289
        }
290
        if ( $votes['absent'] ) {
291
            $actions[] = $votes['absent'] . ' ' . make_plural('absence', $votes['absent']);
292
        }
293
        if ($max == $min) {
294
            return join(', ', $actions) . ', in ' . $max;
295
        } else {
296
            return join(', ', $actions) . ', between ' . $min . '&ndash;' . $max;
297
        }
298
    }
299
300
    /**
301
     *
302
     * Get all the divisions a member has voted in keyed by policy
303
     *
304
     * Returns an array with keys for each policyID, each of these contains
305
     * the same structure as getMemberDivisionsForPolicy
306
     *
307
     */
308
309
    public function getAllMemberDivisionsByPolicy() {
310
        return $this->getMemberDivisionsForPolicy();
311
    }
312
313
314
    /**
315
     * Get the last n votes for a member
316
     *
317
     * @param $number int - How many divisions to return. Defaults to 20
318
     * @param $context string - The context of the page the results are being presented in.
319
     *    This affects the summary details and can either be 'Parliament' in which case the
320
     *    overall vote for all MPs is returned, plus additional information on how the MP passed
321
     *    in to the constructor voted, or the default of 'MP' which is just the vote of the
322
     *    MP passed in to the constructor.
323
     *
324
     * Returns an array of divisions
325
     */
326
    public function getRecentMemberDivisions($number = 20, $context = 'MP') {
327
        $args = array(':person_id' => $this->member->person_id, ':number' => $number);
328
        $q = $this->db->query(
329
            "SELECT division_id, division_title, yes_text, no_text, division_date, division_number, vote, gid, direction,
330
            yes_total, no_total, absent_total, both_total, majority_vote
331
            FROM policydivisions JOIN persondivisionvotes USING(division_id)
332
            WHERE person_id = :person_id
333
            GROUP BY division_id
334
            ORDER by division_date DESC, division_id DESC LIMIT :number",
335
            $args
336
        );
337
338
        $divisions = array();
339
        $row_count = $q->rows();
340
        for ($n = 0; $n < $row_count; $n++) {
341
          if ($context == 'Parliament') {
342
              $divisions[] = $this->getParliamentDivisionDetails($q->row($n));
343
          } else {
344
              $divisions[] = $this->getDivisionDetails($q->row($n));
345
          }
346
        }
347
348
        return $divisions;
349
    }
350
351
352
    private function constructYesNoVoteDescription($direction, $title, $short_text) {
353
        $text = ' voted ';
354
        if ( $short_text ) {
355
            $text .= $short_text;
356
        } else {
357
            $text .= "$direction on <em>$title</em>";
358
        }
359
360
        return $text;
361
    }
362
363
364
    private function constructVoteDescription($vote, $yes_text, $no_text, $division_title) {
365
        /*
366
         * for most post 2010 votes we have nice single sentence summaries of
367
         * what voting for or against means so we use that if it's there, however
368
         * we don't have anything nice for people being absent or for pre 2010
369
         * votes so we need to generate some text using the title of the division
370
         */
371
372
        switch ( strtolower($vote) ) {
373
            case 'yes':
374
            case 'aye':
375
                $description = $this->constructYesNoVoteDescription('yes', $division_title, $yes_text);
376
                break;
377
            case 'no':
378
                $description = $this->constructYesNoVoteDescription('no', $division_title, $no_text);
379
                break;
380
            case 'absent':
381
                $description = ' was absent for a vote on <em>' . $division_title . '</em>';
382
                break;
383
            case 'both':
384
                $description = ' abstained on a vote on <em>' . $division_title . '</em>';
385
                break;
386
            case 'tellyes':
387
            case 'tellno':
388
            case 'tellaye':
389
                $description = ' acted as teller for a vote on <em>' . $division_title . '</em>';
390
                break;
391
            default:
392
                $description = $division_title;
393
        }
394
395
        return $description;
396
    }
397
398
    private function getBasicDivisionDetails($row, $vote) {
399
        $division = array();
400
401
        $direction = $row['direction'];
402
        if ( strpos( $direction, 'strong') !== FALSE ) {
403
            $division['strong'] = TRUE;
404
        } else {
405
            $division['strong'] = FALSE;
406
        }
407
408
        $division['division_id'] = $row['division_id'];
409
        $division['date'] = $row['division_date'];
410
        $division['gid'] = fix_gid_from_db($row['gid']);
411
        $division['url'] = $this->divisionUrlFromGid($row['gid']);
412
        $division['direction'] = $direction;
413
        $division['number'] = $row['division_number'];
414
415
        $yes_text = $row['yes_text'];
416
        $no_text = $row['no_text'];
417
        $division_title = $row['division_title'];
418
        $division['text'] = $this->constructVoteDescription($vote, $yes_text, $no_text, $division_title);
419
420
        $division['has_description'] = false;
421
        if ($yes_text && $no_text) {
422
            $division['has_description'] = true;
423
        }
424
425
        if ($row['gid']) {
426
            $division['debate_url'] = $this->divisionUrlFromGid($row['gid']);
427
        }
428
429
        return $division;
430
    }
431
432
    private function getDivisionDetails($row) {
433
        $vote = $row['vote'];
434
        $division = $this->getBasicDivisionDetails($row, $vote);
435
436
        $division['vote'] = $vote;
437
438
        return $division;
439
    }
440
441
    private function getParliamentDivisionDetails($row) {
442
        $vote = $row['majority_vote'];
443
        $division = $this->getBasicDivisionDetails($row, $vote);
444
445
        $division['mp_vote'] = '';
446
        if (array_key_exists('vote', $row)) {
447
          $mp_vote = ' was absent';
448
          if ($row['vote'] == 'aye') {
449
              $mp_vote = 'voted in favour';
450
          } else if ($row['vote'] == 'no') {
451
              $mp_vote = 'voted against';
452
          }
453
          $division['mp_vote'] = $mp_vote;
454
        }
455
        $division['division_title'] = $row['division_title'];
456
        $division['vote'] = $vote;
457
458
        $division['summary'] = $row['yes_total'] . ' for, ' . $row['no_total'] . ' against, ' . $row['absent_total'] . ' absent';
459
        $division['for'] = $row['yes_total'];
460
        $division['against'] = $row['no_total'];
461
        $division['both'] = $row['both_total'];
462
        $division['absent'] = $row['absent_total'];
463
464
        return $division;
465
    }
466
467
    private function divisionsByPolicy($q) {
468
        $policies = array();
469
470
        $row_count = $q->rows();
471
        for ($n = 0; $n < $row_count; $n++) {
472
            $policy_id = $q->field($n, 'policy_id');
473
474
            if ( !array_key_exists($policy_id, $policies) ) {
475
                $policies[$policy_id] = array(
476
                    'policy_id' => $policy_id,
477
                    'weak_count' => 0,
478
                    'divisions' => array()
479
                );
480
                if ( $this->policies ) {
481
                    $policies[$policy_id]['desc'] = $this->policies->getPolicies()[$policy_id];
482
                    $policies[$policy_id]['header'] = $this->policies->getPolicyDetails($policy_id);
483
                }
484
                if ( $this->positions ) {
485
                    $policies[$policy_id]['position'] = $this->positions->positionsById[$policy_id];
486
                }
487
            }
488
489
            $division = $this->getDivisionDetails($q->row($n));
490
491
            if ( !$division['strong'] ) {
492
                $policies[$policy_id]['weak_count']++;
493
            }
494
495
            $policies[$policy_id]['divisions'][] = $division;
496
        };
497
498
        return $policies;
499
    }
500
501
    private function divisionUrlFromGid($gid) {
502
        global $hansardmajors;
503
504
        $gid = get_canonical_gid($gid);
505
506
        $q = $this->db->query("SELECT gid, major FROM hansard WHERE epobject_id = ( SELECT subsection_id FROM hansard WHERE gid = :gid )", array( ':gid' => $gid ));
507
        $parent_gid = $q->field(0, 'gid');
508
        if ( !$parent_gid ) {
509
            return '';
510
        }
511
        $parent_gid = fix_gid_from_db($parent_gid);
512
        $url = new \URL($hansardmajors[$q->field(0, 'major')]['page']);
513
        $url->insert(array('gid' => $parent_gid));
514
        return $url->generate();
515
    }
516
}
517