HANSARDLIST::render()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 3
dl 0
loc 11
ccs 0
cts 4
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
include_once INCLUDESPATH . "easyparliament/searchengine.php";
4
include_once INCLUDESPATH . "easyparliament/searchlog.php";
5
6
/*
7
8
The HANSARDLIST class and its children, DEBATELIST and WRANSLIST, display data about
9
Hansard objects. You call display things by doing something like:
10
11
        $LIST = new DEBATELIST;
12
        $LIST->display('gid', array('gid'=>'2003-10-30.422.4') );
13
14
    The second line could be replaced with something like one of these:
15
16
        $LIST->display('date', array('date'=>'2003-12-31') );
17
        $LIST->display('recent');
18
        $LIST->display('member', array('id'=>37) );
19
20
21
Basic structure...
22
23
    The display() function calls a get function which returns all the data that
24
    is to be displayed. The function name depends on the $view (ie, 'gid', 'recent',
25
    etc).
26
    Once we have an array of data, the render() function is called, which includes
27
    a template. This cycles through the data array and outputs HTML.
28
29
    Most of the data is fetched from the database by the _get_hansard_data() function.
30
31
    The COMMENTSLIST class is simpler and works in a similar fashion - that might help
32
    you get your head round how this all works...
33
34
Future stuff...
35
36
    You could have multiple templates for different formats. Eg, to output stuff in
37
    XML, duplicate the HTML template and change what you need to create XML instead.
38
    Then call the display() function something like this:
39
        $LIST->display('gid', array('gid'=>'2003-10-30.422.4'), 'xml' );
40
    (You'll need to allow the 'xml' format in render() too).
41
42
    No support for pages of results yet. This would be passed in in the $args array
43
    and used in the LIMIT of the _get_hansard_data() function.
44
    The template could then display links to next/prev pages in the sequence.
45
46
47
48
49
*/
50
51
class RedirectException extends \Exception {}
52
53
class HANSARDLIST {
54
    // This will be used to cache information about speakers on this page
55
    // so we don't have to keep fetching the same data from the DB.
56
    public $speakers =  [];
57
    /*
58
    $this->speakers[ $person_id ] = array (
59
        "name" => $name,
60
        "constituency"	=> $constituency,
61
        "party"			=> $party,
62
        "person_id"	    => $person_id,
63
        "url"			=> "/member/?p=$person_id"
64
    );
65
    */
66
67
    // This will be used to cache mappings from epobject_id to gid,
68
    // so we don't have to continually fetch the same data in get_hansard_data().
69
    public $epobjectid_to_gid =  [];
70
    /*
71
    $this->epobjectid_to_gid[ $epobject_id ] => $gid;
72
    */
73
74
    # Similarly, cache bill lookups
75
    public $bill_lookup = [];
76
77
    // This is so we can tell what type of thing we're displaying from outside
78
    // the object. eg, so we know if we should be able to post comments to the
79
    // item. It will have a value set if we are displaying by 'gid' (not 'date').
80
    // Use htype() to access it.
81
    public $htype;
82
83
84
    // Reset to the relevant major ID in DEBATELIST or WRANSLIST
85
    public $major;
86
87
88
    // When we view a particular item, we set these to the epobject_id and gid
89
    // of the item so we can attach things to it from outside.
90
    public $epobject_id;
91
    public $gid;
92
93
94
    // This will be set if $this->most_recent_day() is called. Just so we
95
    // don't need to call it and it's lengthy query again.
96
    public $most_recent_day;
97
98
    // This prefix is used to pick out unique things by type
99
    public $gidprefix;
100
101
    // These are used to specify the pages for each subclass
102
    public $listpage;
103
    public $commentspage;
104
105
    # Only used by StandingCommittee subclass
106
    public $bill_title;
107
    public $url;
108
109
    public $db;
110
111 6
    public function __construct() {
112 6
        $this->db = new ParlDB();
113 6
        $this->after_left = json_decode(ENTRIES_AFTER_LEFT, 1) ?? [];
0 ignored issues
show
Bug Best Practice introduced by
The property after_left does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
Bug introduced by
The constant ENTRIES_AFTER_LEFT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
114
    }
115
116
117
118
    public function display($view, $args = [], $format = 'html') {
119
120
        // $view is what we're viewing by:
121
        // 	'gid' is the gid of a hansard object,
122
        //	'date' is all items on a date,
123
        //	'person' is a person's recent debates/wrans,
124
        //	'recent' is a number of recent dates with items in.
125
        //  'recent_mostvotes' is the speeches with the most votes in the last x days.
126
        //	'search' is all debates/wrans that match a search term.
127
        //	'biggest_debates' is biggest recent debates (obviously only for DEBATESLIST).
128
        //  'recent_wrans' is some recent written answers (obv only for WRANSLIST).
129
130
        // $args is an associative array of stuff like
131
        //	'gid' => '2003-10-30.422.4'  or
132
        //	'd' => '2003-12-31' or
133
        //	's' => 'my search term'
134
        //	'o' => Sort order: 'r' for relevance, 'd' for date
135
136
        // $format is the format the data should be rendered in,
137
        // using that set of templates (or 'none' for just returning
138
        // the data).
139
140
        global $PAGE;
141
142
        if ($view == 'search' && (!defined('FRONT_END_SEARCH') || !FRONT_END_SEARCH)) {
143
            return false;
144
        }
145
146
        $validviews =  ['calendar', 'date', 'gid', 'person', 'search', 'recent', 'recent_mostvotes', 'biggest_debates', 'recent_wrans', 'recent_wms', 'column', 'mp', 'bill', 'session', 'recent_debates', 'recent_pbc_debates', 'featured_gid'];
147
        if (in_array($view, $validviews)) {
148
149
            // What function do we call for this view?
150
            $function = '_get_data_by_' . $view;
151
            // Get all the data that's to be rendered.
152
            $data = $this->$function($args);
153
154
        } else {
155
            // Don't have a valid $view.
156
            $PAGE->error_message("You haven't specified a view type.");
157
            return false;
158
        }
159
160
        // Set the values of this page's headings depending on the data we've fetched.
161
        if (isset($PAGE) && isset($data['info'])) {
162
            $PAGE->set_hansard_headings($data['info']);
163
        }
164
165
        // Glossary $view_override (to avoid too much code duplication...)
166
        if (isset($args['view_override'])) {
167
            $view = $args['view_override'];
168
        }
169
170
        $return = $this->render($view, $data, $format);
171
172
        return $return;
173
    }
174
175
176
177
    public function render($view, $data, $format = 'html') {
178
        // Once we have the data that's to be rendered,
179
        // include the template.
180
181
        // No format, so don't use the template sets.
182
        if ($format == 'none') {
183
            return $data;
184
        }
185
186
        include(INCLUDESPATH . "easyparliament/templates/$format/hansard_$view" . ".php");
187
        return true;
188
189
    }
190
191
192
    public function total_items() {
193
        // Returns number of items in debates or wrans, depending on which class this is,
194
        // DEBATELIST or WRANSLIST.
195
196
        $q = $this->db->query("SELECT COUNT(*) AS count FROM hansard WHERE major = :major", [':major' => $this->major]);
197
198
        return $q->first()['count'];
199
    }
200
201
202 1
203
    public function most_recent_day() {
204
        // Very simple. Returns an array of stuff about the most recent data
205
        // for this major:
206
207
        // array (
208
        //		'hdate'		=> 'YYYY-MM-DD',
209
        //		'timestamp' => 124453679,
210
        //		'listurl'	=> '/foo/?id=bar'
211
        // )
212
213
        // When we do this function the first time we cache the
214 1
        // results in this variable. As it's an expensive query.
215
        if (isset($this->most_recent_day)) {
216
            return $this->most_recent_day;
217
        }
218
219 1
        // What we return.
220
        $data = [];
221 1
222
        $q = $this->db->query("SELECT MAX(hdate) AS hdate
223
                        FROM 	hansard
224 1
                        WHERE	major = :major
225 1
                        ", [':major' => $this->major])->first();
226
        if ($q) {
227 1
228 1
            $hdate = $q['hdate'];
229 1
            if ($hdate) {
230 1
                $URL = new \MySociety\TheyWorkForYou\Url($this->listpage);
231
                $URL->insert(['d' => $hdate]);
232
233 1
                // Work out a timestamp which is handy for comparing to now.
234 1
                [$year, $month, $date] = explode('-', $hdate);
235
                $timestamp = gmmktime(0, 0, 0, $month, $date, $year);
0 ignored issues
show
Bug introduced by
$year of type string is incompatible with the type integer expected by parameter $year of gmmktime(). ( Ignorable by Annotation )

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

235
                $timestamp = gmmktime(0, 0, 0, $month, $date, /** @scrutinizer ignore-type */ $year);
Loading history...
Bug introduced by
$date of type string is incompatible with the type integer expected by parameter $day of gmmktime(). ( Ignorable by Annotation )

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

235
                $timestamp = gmmktime(0, 0, 0, $month, /** @scrutinizer ignore-type */ $date, $year);
Loading history...
Bug introduced by
$month of type string is incompatible with the type integer expected by parameter $month of gmmktime(). ( Ignorable by Annotation )

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

235
                $timestamp = gmmktime(0, 0, 0, /** @scrutinizer ignore-type */ $month, $date, $year);
Loading history...
236
237 1
                $data =  [
238 1
                    'hdate'		=> $hdate,
239 1
                    'timestamp'	=> $timestamp,
240
                    'listurl'	=> $URL->generate(),
241
                ];
242
243
                // This is just because it's an expensive query
244
                // and we really want to avoid doing it more than once.
245 1
                // So we're caching it.
246
                $this->most_recent_day = $data;
247
            }
248
        }
249 1
250
        return $data;
251
    }
252
253
254
    public function htype() {
255
        return $this->htype;
256
    }
257
258
    public function epobject_id() {
259
        return $this->epobject_id;
260
    }
261
262
    public function gid() {
263
        return $this->gid;
264
    }
265
266 2
267
    public function _get_section($itemdata) {
268
        // Pass it an array of data about an item and it will return an
269
        // array of data about the item's section heading.
270 2
271
        twfy_debug(get_class($this), "getting an item's section");
272 2
273
        if ($itemdata['htype'] != '10') {
274
275
            // This item is a subsection, speech or procedural,
276
            // or a wrans questions/answer,
277
            // so get the section info above this item.
278
279
            // For getting hansard data.
280
            $input =  [
281
                'amount' =>  [
282
                    'body' => true,
283
                ],
284
                'where' =>  [
285
                    'hansard.epobject_id=' => $itemdata['section_id'],
286
                ],
287
            ];
288
289
            $sectiondata = $this->_get_hansard_data($input);
290
291
            if (count($sectiondata) > 0) {
292
                $sectiondata = $sectiondata[0];
293
            }
294
295
        } else {
296
            // This item *is* a section, so just return that.
297 2
298
            $sectiondata = $itemdata;
299
300
        }
301 2
302
        return $sectiondata;
303
    }
304
305
306 1
307
    public function _get_subsection($itemdata) {
308
        // Pass it an array of data about an item and it will return an
309
        // array of data about the item's subsection heading.
310 1
311
        twfy_debug(get_class($this), "getting an item's subsection");
312
313 1
        // What we return.
314
        $subsectiondata =  [];
315 1
316
        if ($itemdata['htype'] == '12' || $itemdata['htype'] == '13' || $itemdata['htype'] == '14') {
317
            // This item is a speech or procedural, so get the
318
            // subsection info above this item.
319
320
            // For getting hansard data.
321
            $input =  [
322
                'amount' =>  [
323
                    'body' => true,
324
                ],
325
                'where' =>  [
326
                    'hansard.epobject_id=' => $itemdata['subsection_id'],
327
                ],
328
            ];
329
330
            $subsectiondata = $this->_get_hansard_data($input);
331
            if (count($subsectiondata) == 0) {
332
                $subsectiondata = null;
333
            } else {
334
                $subsectiondata = $subsectiondata[0];
335
            }
336 1
337
        } elseif ($itemdata['htype'] == '11') {
338
            // It's a subsection, so use the item itself.
339
            $subsectiondata = $itemdata;
340
        }
341 1
342
        return $subsectiondata;
343
    }
344
345
346 1
347 1
    public function _get_nextprev_items($itemdata) {
348
        global $hansardmajors;
349
350
        // Pass it an array of item info, of a section/subsection, and this will return
351
        // data for the next/prev items.
352 1
353
        twfy_debug(get_class($this), "getting next/prev items");
354
355 1
        // What we return.
356
        $nextprevdata = [];
357 1
358 1
        $prev_item_id = false;
359
        $next_item_id = false;
360 1
361
        if ($itemdata['htype'] == '10' || $itemdata['htype'] == '11') {
362 1
            // Debate subsection or section - get the next one.
363
            if ($hansardmajors[$itemdata['major']]['type'] == 'other' && $hansardmajors[$itemdata['major']]['location'] == 'UK') {
364
                $where = 'htype = 11';
365 1
            } else {
366
                $where = "(htype = 10 OR htype = 11)";
367
            }
368
        } else {
369
            // Anything else in debates - get the next element that isn't
370
            // a subsection or section, and is within THIS subsection.
371
            $where = "subsection_id = '" . $itemdata['subsection_id'] . "' AND (htype != 10 AND htype != 11)";
372
        }
373
374
        // Find if there are next/previous debate items of our
375
        // chosen type today.
376
377
        // For sections/subsections,
378
        // this will find headings with no content, but I failed to find
379
        // a vaguely simple way to do this. So this is it for now...
380
381 1
        // Find the epobject_id of the previous item (if any):
382
        $q = $this->db->query("SELECT epobject_id
383 1
                        FROM 	hansard
384 1
                        WHERE 	hdate = '" . $itemdata['hdate'] . "'
385 1
                        AND 	hpos < '" . $itemdata['hpos'] . "'
386 1
                        AND 	major = '" . $itemdata['major'] . "'
387
                        AND 	$where
388 1
                        ORDER BY hpos DESC
389
                        LIMIT 1")->first();
390 1
391 1
        if ($q) {
392
            $prev_item_id = $q['epobject_id'];
393
        }
394
395 1
        // Find the epobject_id of the next item (if any):
396
        $q = $this->db->query("SELECT epobject_id
397 1
                        FROM 	hansard
398 1
                        WHERE 	hdate = '" . $itemdata['hdate'] . "'
399 1
                        AND 	hpos > '" . $itemdata['hpos'] . "'
400 1
                        AND 	major = '" . $itemdata['major'] . "'
401
                        AND 	$where
402 1
                        ORDER BY hpos ASC
403
                        LIMIT 1")->first();
404 1
405 1
        if ($q) {
406
            $next_item_id = $q['epobject_id'];
407
        }
408
409
        // Now we're going to get the data for the next and prev items
410
        // that we will use to make the links on the page.
411
412 1
        // Previous item.
413
        if ($prev_item_id) {
414 1
            // We have a previous one to link to.
415 1
            $wherearr = [];
416
            $wherearr['hansard.epobject_id='] = $prev_item_id;
417
418
            // For getting hansard data.
419
            $input =  [
420 1
                'amount' =>  [
421
                    'body' => true,
422
                    'speaker' => true,
423 1
                ],
424 1
                'where' => $wherearr,
425 1
                'order' => 'hpos DESC',
426
                'limit' => 1,
427
            ];
428 1
429
            $prevdata = $this->_get_hansard_data($input);
430 1
431 1
            if (count($prevdata) > 0) {
432
                if ($itemdata['htype'] == '10' || $itemdata['htype'] == '11') {
433 1
                    // Linking to the prev (sub)section.
434 1
                    $thing = $hansardmajors[$this->major]['singular'];
435 1
                    $nextprevdata['prev'] =  [
436 1
                        'body'		=> sprintf(gettext("Previous %s"), $thing),
437 1
                        'url'		=> $prevdata[0]['listurl'],
438
                        'title'		=> $prevdata[0]['body'],
439
                    ];
440
                } else {
441
                    // Linking to the prev speaker.
442
443
                    if (isset($prevdata[0]['speaker']) && count($prevdata[0]['speaker']) > 0) {
444
                        $title = $prevdata[0]['speaker']['name'];
445
                    } else {
446
                        $title = '';
447
                    }
448
                    $nextprevdata['prev'] =  [
449
                        'body'		=> gettext('Previous speaker'),
450
                        'url'		=> $prevdata[0]['commentsurl'],
451
                        'title'		=> $title,
452
                    ];
453
                }
454
            }
455
        }
456
457 1
        // Next item.
458
        if ($next_item_id) {
459 1
            // We have a next one to link to.
460 1
            $wherearr = [];
461
            $wherearr['hansard.epobject_id='] = $next_item_id;
462
463
            // For getting hansard data.
464
            $input =  [
465 1
                'amount' =>  [
466
                    'body' => true,
467
                    'speaker' => true,
468 1
                ],
469 1
                'where' => $wherearr,
470 1
                'order' => 'hpos ASC',
471
                'limit' => 1,
472 1
            ];
473
            $nextdata = $this->_get_hansard_data($input);
474 1
475 1
            if (count($nextdata) > 0) {
476
                if ($itemdata['htype'] == '10' || $itemdata['htype'] == '11') {
477 1
                    // Linking to the next (sub)section.
478 1
                    $thing = $hansardmajors[$this->major]['singular'];
479 1
                    $nextprevdata['next'] =  [
480 1
                        'body'		=> sprintf(gettext("Next %s"), $thing),
481 1
                        'url'		=> $nextdata[0]['listurl'],
482
                        'title'		=> $nextdata[0]['body'],
483
                    ];
484
                } else {
485
                    // Linking to the next speaker.
486
487
                    if (isset($nextdata[0]['speaker']) && count($nextdata[0]['speaker']) > 0) {
488
                        $title = $nextdata[0]['speaker']['name'];
489
                    } else {
490
                        $title = '';
491
                    }
492
                    $nextprevdata['next'] =  [
493
                        'body'		=> gettext('Next speaker'),
494
                        'url'		=> $nextdata[0]['commentsurl'],
495
                        'title'		=> $title,
496
                    ];
497
                }
498
            }
499
        }
500 1
501
        if ($this->major == 6) {
502
            $URL = new \MySociety\TheyWorkForYou\Url('pbc_bill');
503
            $URL->remove(['bill']);
504
            $nextprevdata['up'] = [
505
                'body'	=> _htmlspecialchars($this->bill_title),
506
                'title'	=> '',
507
                'url'	=> $URL->generate() . $this->url,
508 1
            ];
509 1
        } elseif ($itemdata['htype'] == '10' || $itemdata['htype'] == '11') {
510
            $URL = new \MySociety\TheyWorkForYou\Url($this->listpage);
511 1
            // Create URL for this (sub)section's date.
512 1
            $URL->insert(['d' => $itemdata['hdate']]);
513 1
            $URL->remove(['id']);
514 1
            $things = $hansardmajors[$itemdata['major']]['title'];
515 1
            $nextprevdata['up'] = [
516 1
                'body'	=> sprintf(gettext("All %s on %s"), $things, format_date($itemdata['hdate'], SHORTDATEFORMAT)),
517 1
                'title'	=> '',
518
                'url' 	=> $URL->generate(),
519
            ];
520
        } else {
521
            // We'll be setting $nextprevdata['up'] within $this->get_data_by_gid()
522
            // because we need to know the name and url of the parent item, which
523
            // we don't have here. Life sucks.
524
        }
525 1
526
        return $nextprevdata;
527
    }
528
529 1
530 1
    public function _get_nextprev_dates($date) {
531
        global $hansardmajors;
532
        // Pass it a yyyy-mm-dd date and it'll return an array
533
        // containing the next/prev dates that contain items from
534
        // $this->major of hansard object.
535 1
536
        twfy_debug(get_class($this), "getting next/prev dates");
537
538 1
        // What we return.
539
        $nextprevdata =  [];
540 1
541
        $URL = new \MySociety\TheyWorkForYou\Url($this->listpage);
542 1
543
        $looper =  ["next", "prev"];
544 1
545
        foreach ($looper as $n => $nextorprev) {
546 1
547
            $URL->reset();
548 1
549 1
            $params = [':major' => $this->major,
550 1
                ':date' => $date];
551 1
            if ($nextorprev == 'next') {
552
                $q = $this->db->query("SELECT MIN(hdate) AS hdate
553
                            FROM 	hansard
554 1
                            WHERE 	major = :major
555
                            AND		hdate > :date", $params)->first();
556 1
            } else {
557
                $q = $this->db->query("SELECT MAX(hdate) AS hdate
558
                            FROM 	hansard
559 1
                            WHERE 	major = :major
560
                            AND		hdate < :date", $params)->first();
561
            }
562
563
            // The '!= NULL' bit is needed otherwise I was getting errors
564 1
            // when displaying the first day of debates.
565
            if ($q && $q['hdate'] != null) {
566 1
567
                $URL->insert([ 'd' => $q['hdate'] ]);
568 1
569 1
                if ($nextorprev == 'next') {
570
                    $body = gettext('Next day');
571
                } else {
572
                    $body = gettext('Previous day');
573
                }
574 1
575
                $title = format_date($q['hdate'], SHORTDATEFORMAT);
576 1
577 1
                $nextprevdata[$nextorprev] =  [
578 1
                    'hdate' => $q['hdate'],
579 1
                    'url' => $URL->generate(),
580 1
                    'body' => $body,
581
                    'title' => $title,
582
                ];
583
            }
584
        }
585 1
586 1
        $year = substr($date, 0, 4);
587 1
        $URL = new \MySociety\TheyWorkForYou\Url($hansardmajors[$this->major]['page_year']);
588 1
        $thing = $hansardmajors[$this->major]['plural'];
589
        $URL->insert(['y' => $year]);
590 1
591 1
        $nextprevdata['up'] =  [
592 1
            'body' 	=> sprintf(gettext("All of %s’s %s"), $year, $thing),
593 1
            'title'	=> '',
594
            'url' 	=> $URL->generate(),
595
        ];
596 1
597
        return $nextprevdata;
598
599
    }
600
601
602 1
603
    public function _validate_date($args) {
604
        // Used when we're viewing things by (_get_data_by_date() functions).
605
        // If $args['date'] is a valid yyyy-mm-dd date, it is returned.
606 1
        // Else false is returned.
607
        global $PAGE;
608 1
609 1
        if (isset($args['date'])) {
610
            $date = $args['date'];
611
        } else {
612
            $PAGE->error_message("Sorry, we don't have a date.");
613
            return false;
614
        }
615 1
616
        if (!preg_match("/^(\d\d\d\d)-(\d{1,2})-(\d{1,2})$/", $date, $matches)) {
617
            $PAGE->error_message("Sorry, '" . _htmlentities($date) . "' isn't of the right format (YYYY-MM-DD).");
618
            return false;
619
        }
620 1
621
        [, $year, $month, $day] = $matches;
622 1
623
        if (!checkdate($month, $day, $year)) {
624
            $PAGE->error_message("Sorry, '" . _htmlentities($date) . "' isn't a valid date.");
625
            return false;
626
        }
627 1
628 1
        $day = substr("0$day", -2);
629 1
        $month = substr("0$month", -2);
630
        $date = "$year-$month-$day";
631
632 1
        // Valid date!
633
        return $date;
634
    }
635
636
637 2
638 2
    public function _get_item($args) {
639
        global $PAGE;
640 2
641
        if (!isset($args['gid']) && $args['gid'] == '') {
642
            $PAGE->error_message("Sorry, we don't have an item gid.");
643
            return false;
644
        }
645
646
647
        // Get all the data just for this epobject_id.
648
        $input =  [
649 2
            'amount' =>  [
650
                'body' => true,
651
                'speaker' => true,
652
                'comment' => true,
653
                'votes' => true,
654
            ],
655
            'where' =>  [
656
                // Need to add the 'uk.org.publicwhip/debate/' or whatever on before
657 2
                // looking in the DB.
658
                'gid=' => $this->gidprefix . $args['gid'],
659
            ],
660
        ];
661 2
662 2
        twfy_debug(get_class($this), "looking for redirected gid");
663 2
        $gid = $this->gidprefix . $args['gid'];
664 2
        $q = $this->db->query("SELECT gid_to FROM gidredirect WHERE gid_from = :gid", [':gid' => $gid])->first();
665 2
        if (!$q) {
666
            $itemdata = $this->_get_hansard_data($input);
667
        } else {
668
            do {
669
                $gid = $q['gid_to'];
670
                $q = $this->db->query("SELECT gid_to FROM gidredirect WHERE gid_from = :gid", [':gid' => $gid])->first();
671
            } while ($q);
672
            twfy_debug(get_class($this), "found redirected gid $gid");
673
            $input['where'] = ['gid=' => $gid];
674
            $itemdata = $this->_get_hansard_data($input);
675
            if (count($itemdata) > 0) {
676
                throw new RedirectException(fix_gid_from_db($gid));
677
            }
678
        }
679 2
680 2
        if (count($itemdata) > 0) {
681
            $itemdata = $itemdata[0];
682
        }
683 2
684
        if (count($itemdata) == 0) {
685
            /* Deal with old links to some Lords pages. Somewhere. I can't remember where */
686
            $this->check_gid_change($args['gid'], 'a', '');
687
688
            if (substr($args['gid'], -1) == 'L') {
689
                $letts = ['a','b','c','d','e'];
690
                for ($i = 0; $i < 4; $i++) {
691
                    $this->check_gid_change($args['gid'], $letts[$i], $letts[$i + 1]);
692
                }
693
            }
694
695
            /* A lot of written answers were moved from 10th to 11th May and 11th May to 12th May.
696
               Deal with the bots who have stored links to those now non-existant written answers. */
697
            /* 2007-05-31: And then they were moved BACK in the volume edition, ARGH */
698
            $this->check_gid_change($args['gid'], '2006-05-10a', '2006-05-10c');
699
            $this->check_gid_change($args['gid'], '2006-05-10a', '2006-05-11d');
700
            $this->check_gid_change($args['gid'], '2006-05-11b', '2006-05-11d');
701
            $this->check_gid_change($args['gid'], '2006-05-11b', '2006-05-12c');
702
            $this->check_gid_change($args['gid'], '2006-05-11c', '2006-05-10c');
703
            $this->check_gid_change($args['gid'], '2006-05-12b', '2006-05-11d');
704
705
            $this->check_gid_change($args['gid'], '2007-01-08', '2007-01-05');
706
            $this->check_gid_change($args['gid'], '2007-02-19', '2007-02-16');
707
708
            /* More movearounds... */
709
            $this->check_gid_change($args['gid'], '2005-10-10d', '2005-09-12a');
710
            $this->check_gid_change($args['gid'], '2005-10-14a', '2005-10-13b');
711
            $this->check_gid_change($args['gid'], '2005-10-18b', '2005-10-10e');
712
            $this->check_gid_change($args['gid'], '2005-11-17b', '2005-11-15c');
713
714
            $this->check_gid_change($args['gid'], '2007-01-08a', '2007-01-08e');
715
716
            /* Right back when Lords began, we sent out email alerts when they weren't on the site. So this was to work that. */
717
            #$lord_gid_like = 'uk.org.publicwhip/lords/' . $args['gid'] . '%';
718
            #$q = $this->db->query('SELECT source_url FROM hansard WHERE gid LIKE :lord_gid_like', array(':lord_gid_like' => $lord_gid_like))->first();
719
            #$u = '';
720
            #if ($q) {
721
            #	$u = $q['source_url'];
722
            #	$u = '<br><a href="'. $u . '">' . $u . '</a>';
723
            #}
724
            $PAGE->error_message("Sorry, there is no Hansard object with a gid of '" . _htmlentities($args['gid']) . "'.");
725
            return false;
726
        }
727 2
728
        return $itemdata;
729
730
    }
731
732
    private function check_gid_change($gid, $from, $to) {
733
        $input =  [
734
            'amount' =>  [
735
                'body' => true,
736
                'speaker' => true,
737
                'comment' => true,
738
                'votes' => true,
739
            ],
740
        ];
741
        if (strstr($gid, $from)) {
742
            $check_gid = str_replace($from, $to, $gid);
743
            $input['where'] = ['gid=' => $this->gidprefix . $check_gid];
744
            $itemdata = $this->_get_hansard_data($input);
745
            if (count($itemdata) > 0) {
746
                throw new RedirectException($check_gid);
747
            }
748
        }
749
    }
750
751 1
752
    public function _get_data_by_date($args) {
753
        // For displaying the section and subsection headings as
754
        // links for an entire day of debates/wrans.
755 1
756
        global $DATA, $this_page;
757 1
758
        twfy_debug(get_class($this), "getting data by date");
759
760 1
        // Where we'll put all the data we want to render.
761
        $data =  [];
762 1
763
        $date = $this->_validate_date($args);
764 1
765
        if ($date) {
766 1
767
            $nextprev = $this->_get_nextprev_dates($date);
768
769 1
            // We can then access this from $PAGE and the templates.
770
            $DATA->set_page_metadata($this_page, 'nextprev', $nextprev);
771
772
773
            // Get all the sections for this date.
774
            // Then for each of those we'll get the subsections and rows.
775
            $input =  [
776 1
                'amount' =>  [
777
                    'body' => true,
778
                    'comment' => true,
779
                    'excerpt' => true,
780
                ],
781 1
                'where' =>  [
782 1
                    'hdate=' => "$date",
783 1
                    'htype=' => '10',
784
                    'major=' => $this->major,
785 1
                ],
786
                'order' => 'hpos',
787
            ];
788 1
789
            $sections = $this->_get_hansard_data($input);
790 1
791
            if (count($sections) > 0) {
792
793 1
                // Where we'll keep the full list of sections and subsections.
794
                $data['rows'] = [];
795 1
796 1
                $num_sections = count($sections);
797
                for ($n = 0; $n < $num_sections; $n++) {
798
                    // For each section on this date, get the subsections within it.
799
800 1
                    // Get all the section data.
801
                    $sectionrow = $this->_get_section($sections[$n]);
802
803
                    // Get the subsections within the section.
804
                    $input =  [
805 1
                        'amount' =>  [
806
                            'body' => true,
807
                            'comment' => true,
808
                            'excerpt' => true,
809
                        ],
810 1
                        'where' =>  [
811 1
                            'section_id='	=> $sections[$n]['epobject_id'],
812 1
                            'htype='		=> '11',
813
                            'major='		=> $this->major,
814 1
                        ],
815
                        'order' => 'hpos',
816
                    ];
817 1
818
                    $rows = $this->_get_hansard_data($input);
819
820 1
                    // Put the section at the top of the rows array.
821
                    array_unshift($rows, $sectionrow);
822
823 1
                    // Add the section heading and the subsections to the full list.
824
                    $data['rows'] = array_merge($data['rows'], $rows);
825
                }
826
            }
827
828 1
            // For page headings etc.
829 1
            $data['info']['date'] = $date;
830
            $data['info']['major'] = $this->major;
831
        }
832 1
833
        return $data;
834
    }
835
836
837
    public function _get_data_by_recent($args) {
838
        // Like _get_data_by_id() and _get_data_by_date()
839
        // this returns a $data array suitable for sending to a template.
840
        // It lists recent dates with debates/wrans on them, with links.
841
842
        $params = [];
843
844
        if (isset($args['days']) && is_numeric($args['days'])) {
845
            $limit = 'LIMIT :limit';
846
            $params[':limit'] = $args['days'];
847
        } else {
848
            $limit = '';
849
        }
850
851
        if ($this->major != '') {
852
            // We must be in DEBATELIST or WRANSLIST.
853
854
            $major = 'WHERE major = :major';
855
            $params[':major'] = $this->major;
856
        } else {
857
            $major = '';
858
        }
859
860
        $data =  [];
861
862
        $q = $this->db->query("SELECT DISTINCT(hdate)
863
                        FROM 	hansard
864
                        $major
865
                        ORDER BY hdate DESC
866
                        $limit
867
                        ", $params);
868
869
        $URL = new \MySociety\TheyWorkForYou\Url($this->listpage);
870
        foreach ($q as $row) {
871
            $URL->insert(['d' => $row['hdate']]);
872
            $data['rows'][] = [
873
                'body' => format_date($row['hdate'], SHORTDATEFORMAT),
874
                'listurl' => $URL->generate(),
875
            ];
876
        }
877
878
        $data['info']['text'] = gettext('Recent dates');
879
880
        return $data;
881
    }
882
883
    # Display a person's most recent debates.
884
    # Only used by MP RSS generator now, MP pages use Xapian search
885
    # XXX: Abolish this entirely?
886 1
887 1
    public function _get_data_by_person($args) {
888 1
        global $PAGE, $hansardmajors;
889
        $items_to_list = $args['max'] ?? 20;
890
891 1
        // Where we'll put all the data we want to render.
892
        $data = [];
893 1
894
        if (!isset($args['person_id'])) {
895
            $PAGE->error_message("Sorry, we need a valid person ID.");
896
            return $data;
897
        }
898 1
899
        $params = [];
900 1
901 1
        $where = 'hansard.person_id = :person_id';
902
        $params[':person_id'] = trim($args['person_id']);
903 1
904
        if (isset($this->major)) {
905
            $majorwhere = "AND hansard.major = :hansard_major ";
906
            $params[':hansard_major'] = $this->major;
907
        } else {
908 1
            // We're getting results for all debates/wrans/etc.
909
            $majorwhere = '';
910
        }
911 1
912
        $q = $this->db->query("SELECT hansard.subsection_id, hansard.section_id,
913
                    hansard.htype, hansard.gid, hansard.major, hansard.minor,
914
                    hansard.hdate, hansard.htime, hansard.person_id,
915
                    epobject.body, epobject_section.body AS body_section,
916
                    epobject_subsection.body AS body_subsection,
917
                                    hansard_subsection.gid AS gid_subsection
918
                FROM hansard
919
                JOIN epobject
920
                    ON hansard.epobject_id = epobject.epobject_id
921
                JOIN epobject AS epobject_section
922
                                    ON hansard.section_id = epobject_section.epobject_id
923
                JOIN epobject AS epobject_subsection
924
                                    ON hansard.subsection_id = epobject_subsection.epobject_id
925
                JOIN hansard AS hansard_subsection
926 1
                                    ON hansard.subsection_id = hansard_subsection.epobject_id
927
                        WHERE	$where $majorwhere
928 1
                        ORDER BY hansard.hdate DESC, hansard.hpos DESC
929
                        LIMIT	$items_to_list
930
                        ", $params);
931
932 1
933 1
        $speeches = [];
934
        foreach ($q as $row) {
935 1
            $speech =  [
936 1
                'subsection_id' => $row['subsection_id'],
937 1
                'section_id' => $row['section_id'],
938 1
                'htype' => $row['htype'],
939 1
                'major' => $row['major'],
940 1
                'minor' => $row['minor'],
941 1
                'hdate' => $row['hdate'],
942 1
                'htime' => $row['htime'],
943 1
                'person_id' => $row['person_id'],
944 1
                'body' => $row['body'],
945 1
                'body_section' => $row['body_section'],
946 1
                'body_subsection' => $row['body_subsection'],
947
                'gid' => fix_gid_from_db($row['gid']),
948
            ];
949 1
            // Cache parent id to speed up _get_listurl
950
            $this->epobjectid_to_gid[$row['subsection_id']] = fix_gid_from_db($row['gid_subsection']);
951 1
952 1
            $url_args =  ['p' => $row['person_id']];
953 1
            $speech['listurl'] = $this->_get_listurl($speech, $url_args);
954
            $speeches[] = $speech;
955
        }
956 1
957
        if (count($speeches) > 0) {
958 1
            // Get the subsection texts.
959 1
            $num_speeches = count($speeches);
960 1
            for ($n = 0; $n < $num_speeches; $n++) {
961
                $thing = $hansardmajors[$speeches[$n]['major']]['title'];
962 1
                // Add the parent's body on...
963 1
                $speeches[$n]['parent']['body'] = $speeches[$n]['body_section'] . ' | ' . $thing;
964 1
                if ($speeches[$n]['subsection_id'] != $speeches[$n]['section_id']) {
965 1
                    $speeches[$n]['parent']['body'] = $speeches[$n]['body_subsection'] .
966
                        ' | ' . $speeches[$n]['parent']['body'];
967
                }
968 1
            }
969
            $data['rows'] = $speeches;
970
        } else {
971
            $data['rows'] = [];
972 1
        }
973
        return $data;
974
    }
975
976
    public function _get_data_by_search($args) {
977
978
        // Creates a fairly standard $data structure for the search function.
979
        // Will probably be rendered by the hansard_search.php template.
980
981
        // $args is an associative array with 's'=>'my search term' and
982
        // (optionally) 'p'=>1  (the page number of results to show) annd
983
        // (optionall) 'pop'=>1 (if "popular" search link, so don't log)
984
        global $PAGE, $hansardmajors;
985
986
        if (isset($args['s'])) {
987
            // $args['s'] should have been tidied up by the time we get here.
988
            // eg, by doing filter_user_input($s, 'strict');
989
            $searchstring = $args['s'];
990
        } else {
991
            if (isset($args['exceptions'])) {
992
                throw new \Exception('No search string provided.');
993
            } else {
994
                $PAGE->error_message("No search string");
995
                return false;
996
            }
997
        }
998
999
        // What we'll return.
1000
        $data =  [];
1001
1002
        $data['info']['s'] = $args['s'];
1003
1004
        // Allows us to specify how many results we want
1005
        // Mainly for glossary term adding
1006
        if (isset($args['num']) && is_numeric($args['num'])) {
1007
            $results_per_page = (int) $args['num'];
1008
        } else {
1009
            $results_per_page = 20;
1010
        }
1011
        if ($results_per_page > 1000) {
1012
            $results_per_page = 1000;
1013
        }
1014
1015
        $data['info']['results_per_page'] = $results_per_page;
1016
1017
        // What page are we on?
1018
        if (isset($args['p']) && is_numeric($args['p'])) {
1019
            $page = $args['p'];
1020
        } else {
1021
            $page = 1;
1022
        }
1023
        $data['info']['page'] = $page;
1024
1025
        if (isset($args['e'])) {
1026
            $encode = 'url';
1027
        } else {
1028
            $encode = 'html';
1029
        }
1030
1031
        // Fetch count of number of matches
1032
        global $SEARCHENGINE;
1033
1034
        // For Xapian's equivalent of an SQL LIMIT clause.
1035
        $first_result = ($page - 1) * $results_per_page;
1036
        $data['info']['first_result'] = $first_result + 1; // Take account of LIMIT's 0 base.
1037
1038
        // Get the gids from Xapian
1039
        $sort_order = 'date';
1040
        if (isset($args['o'])) {
1041
            if ($args['o'] == 'd') {
1042
                $sort_order = 'newest';
1043
            }
1044
            if ($args['o'] == 'o') {
1045
                $sort_order = 'oldest';
1046
            } elseif ($args['o'] == 'c') {
1047
                $sort_order = 'created';
1048
            } elseif ($args['o'] == 'r') {
1049
                $sort_order = 'relevance';
1050
            }
1051
        }
1052
1053
        $data['searchdescription'] = $SEARCHENGINE->query_description_long();
1054
        $count = $SEARCHENGINE->run_count($first_result, $results_per_page, $sort_order);
1055
        $data['info']['total_results'] = $count;
1056
        $data['info']['spelling_correction'] = $SEARCHENGINE->get_spelling_correction();
1057
1058
        // Log this query so we can improve them - if it wasn't a "popular
1059
        // query" link
1060
        if (! isset($args['pop']) or $args['pop'] != 1) {
1061
            global $SEARCHLOG;
1062
            $SEARCHLOG->add(
1063
                ['query' => $searchstring,
1064
                    'page' => $page,
1065
                    'hits' => $count]
1066
            );
1067
        }
1068
        // No results.
1069
        if ($count <= 0) {
1070
            $data['rows'] = [];
1071
            return $data;
1072
        }
1073
1074
        $SEARCHENGINE->run_search($first_result, $results_per_page, $sort_order);
1075
        $gids = $SEARCHENGINE->get_gids();
1076
        if ($sort_order == 'created') {
1077
            $createds = $SEARCHENGINE->get_createds();
1078
        }
1079
        $relevances = $SEARCHENGINE->get_relevances();
1080
        if (count($gids) <= 0) {
1081
            // No results.
1082
            $data['rows'] = [];
1083
            return $data;
1084
        }
1085
        #if ($sort_order=='created') { print_r($gids); }
1086
1087
        // We'll put all the data in here before giving it to a template.
1088
        $rows = [];
1089
1090
        // Cycle through each result, munge the data, get more, and put it all in $data.
1091
        $num_gids = count($gids);
1092
        for ($n = 0; $n < $num_gids; $n++) {
1093
            $gid = $gids[$n];
1094
            $relevancy = $relevances[$n];
1095
            $collapsed = $SEARCHENGINE->collapsed[$n];
1096
            if ($sort_order == 'created') {
1097
                #$created = substr($createds[$n], 0, strpos($createds[$n], ':'));
1098
                if ($createds[$n] < $args['threshold']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $createds does not seem to be defined for all execution paths leading up to this point.
Loading history...
1099
                    $data['info']['total_results'] = $n;
1100
                    break;
1101
                }
1102
            }
1103
1104
            if (strstr($gid, 'calendar')) {
1105
                $id = fix_gid_from_db($gid);
1106
1107
                $itemdata = \MySociety\TheyWorkForYou\Utility\Calendar::fetchItem($id);
1108
                if (!$itemdata) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $itemdata 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...
1109
                    continue;
1110
                }
1111
                $itemdata = array_pop($itemdata); # day
1112
                $itemdata = array_pop($itemdata); # chamber
1113
                $itemdata = array_pop($itemdata); # event
1114
1115
                # Ignore past events in places that we cover (we'll have the data from Hansard)
1116
                if ($itemdata['event_date'] < date('Y-m-d') &&
1117
                    in_array($itemdata['chamber'], [
1118
                        'Commons: Main Chamber', 'Lords: Main Chamber',
1119
                        'Commons: Westminster Hall',
1120
                    ])) {
1121
                    continue;
1122
                }
1123
1124
                [$cal_item, $cal_meta] = \MySociety\TheyWorkForYou\Utility\Calendar::meta($itemdata);
1125
                $body = $this->prepare_search_result_for_display($cal_item) . '.';
1126
                if (!empty($cal_meta)) {
1127
                    $body .= ' <span class="future_meta">' . join('; ', $cal_meta) . '</span>';
1128
                }
1129
                if ($itemdata['witnesses']) {
1130
                    $body .= '<br><small>Witnesses: '
1131
                        . $this->prepare_search_result_for_display($itemdata['witnesses'])
1132
                        . '</small>';
1133
                }
1134
1135
                if ($itemdata['event_date'] >= date('Y-m-d')) {
1136
                    $title = 'Upcoming Business';
1137
                } else {
1138
                    $title = 'Previous Business';
1139
                }
1140
                $itemdata['gid']            = $id;
1141
                $itemdata['relevance']      = $relevancy;
1142
                $itemdata['parent']['body'] = $title . ' &#8211; ' . $itemdata['chamber'];
1143
                $itemdata['extract']        = $body;
1144
                $itemdata['listurl']        = '/calendar/?d=' . $itemdata['event_date'] . '#cal' . $itemdata['id'];
1145
                $itemdata['major']          = 'F';
1146
                $itemdata['hdate']          = $itemdata['event_date'];
1147
1148
            } else {
1149
1150
                $q = ['gid_to' => $gid];
1151
                do {
1152
                    $gid = $q['gid_to'];
1153
                    $q = $this->db->query("SELECT gid_to FROM gidredirect WHERE gid_from = :gid", [':gid' => $gid])->first();
1154
                } while ($q);
1155
1156
                // Get the data for the gid from the database
1157
                $q = $this->db->query("SELECT hansard.gid, hansard.hdate,
1158
                    hansard.htime, hansard.section_id, hansard.subsection_id,
1159
                    hansard.htype, hansard.major, hansard.minor,
1160
                    hansard.person_id, hansard.hpos,
1161
                    epobject.epobject_id, epobject.body
1162
                FROM hansard, epobject
1163
                WHERE hansard.gid = :gid
1164
                    AND hansard.epobject_id = epobject.epobject_id", [':gid' => $gid]);
1165
1166
                if ($q->rows() > 1) {
1167
                    if ($isset($args['exceptions'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $isset seems to be never defined.
Loading history...
1168
                        throw new \Exception("Got more than one row getting data for $gid.");
1169
                    } else {
1170
                        $PAGE->error_message("Got more than one row getting data for $gid");
1171
                    }
1172
                }
1173
                if ($q->rows() == 0) {
1174
                    # This error message is totally spurious, so don't show it
1175
                    # $PAGE->error_message("Unexpected missing gid $gid while searching");
1176
                    continue;
1177
                }
1178
1179
                $itemdata = $q->first();
1180
                $itemdata['collapsed']  = $collapsed;
1181
                $itemdata['gid']        = fix_gid_from_db($itemdata['gid']);
1182
                $itemdata['relevance']  = $relevancy;
1183
                $itemdata['extract']    = $this->prepare_search_result_for_display($itemdata['body']);
1184
1185
                //////////////////////////
1186
                // 2. Create the URL to link to this bit of text.
1187
1188
                $id_data =  [
1189
                    'major'            => $itemdata['major'],
1190
                    'minor'            => $itemdata['minor'],
1191
                    'htype'         => $itemdata['htype'],
1192
                    'gid'             => $itemdata['gid'],
1193
                    'section_id'    => $itemdata['section_id'],
1194
                    'subsection_id'    => $itemdata['subsection_id'],
1195
                ];
1196
1197
                // We append the query onto the end of the URL as variable 's'
1198
                // so we can highlight them on the debate/wrans list page.
1199
                $url_args =  ['s' => $searchstring];
1200
1201
                $itemdata['listurl'] = $this->_get_listurl($id_data, $url_args, $encode);
1202
1203
                //////////////////////////
1204
                // 3. Get the speaker for this item, if applicable.
1205
                if ($itemdata['person_id'] != 0) {
1206
                    $itemdata['speaker'] = $this->_get_speaker($itemdata['person_id'], $itemdata['hdate'], $itemdata['htime'], $itemdata['major']);
1207
                }
1208
1209
                //////////////////////////
1210
                // 4. Get data about the parent (sub)section.
1211
                if ($itemdata['major'] && $hansardmajors[$itemdata['major']]['type'] == 'debate') {
1212
                    // Debate
1213
                    if ($itemdata['htype'] != 10) {
1214
                        $section = $this->_get_section($itemdata);
1215
                        $itemdata['parent']['body'] = $section['body'];
1216
                        #                        $itemdata['parent']['listurl'] = $section['listurl'];
1217
                        if ($itemdata['section_id'] != $itemdata['subsection_id']) {
1218
                            $subsection = $this->_get_subsection($itemdata);
1219
                            $itemdata['parent']['body'] .= ': ' . $subsection['body'];
1220
                            #                            $itemdata['parent']['listurl'] = $subsection['listurl'];
1221
                        }
1222
                        if ($itemdata['major'] == 5) {
1223
                            $itemdata['parent']['body'] = gettext('Northern Ireland Assembly') . ': ' . $itemdata['parent']['body'];
1224
                        } elseif ($itemdata['major'] == 6) {
1225
                            $itemdata['parent']['body'] = gettext('Public Bill Committee') . ': ' . $itemdata['parent']['body'];
1226
                        } elseif ($itemdata['major'] == 7) {
1227
                            $itemdata['parent']['body'] = gettext('Scottish Parliament') . ': ' . $itemdata['parent']['body'];
1228
                        }
1229
1230
                    } else {
1231
                        // It's a section, so it will be its own title.
1232
                        $itemdata['parent']['body'] = $itemdata['body'];
1233
                        $itemdata['body'] = '';
1234
                    }
1235
1236
                } else {
1237
                    // Wrans or WMS
1238
                    $section = $this->_get_section($itemdata);
1239
                    $subsection = $this->_get_subsection($itemdata);
1240
                    $body = $hansardmajors[$itemdata['major']]['title'] . ' &#8212; ';
1241
                    if (isset($section['body'])) {
1242
                        $body .= $section['body'];
1243
                    }
1244
                    if (isset($subsection['body'])) {
1245
                        $body .= ': ' . $subsection['body'];
1246
                    }
1247
                    if (isset($subsection['listurl'])) {
1248
                        $listurl = $subsection['listurl'];
1249
                    } else {
1250
                        $listurl = '';
1251
                    }
1252
                    $itemdata['parent'] =  [
1253
                        'body' => $body,
1254
                        'listurl' => $listurl,
1255
                    ];
1256
                    if ($itemdata['htype'] == 11) {
1257
                        # Search result was a subsection heading; fetch the first entry
1258
                        # from the wrans/wms to show under the heading
1259
                        $input =  [
1260
                            'amount' => [
1261
                                'body' => true,
1262
                                'speaker' => true,
1263
                            ],
1264
                            'where' => [
1265
                                'hansard.subsection_id=' => $itemdata['epobject_id'],
1266
                            ],
1267
                            'order' => 'hpos ASC',
1268
                            'limit' => 1,
1269
                        ];
1270
                        $ddata = $this->_get_hansard_data($input);
1271
                        if (count($ddata)) {
1272
                            $itemdata['body'] = $ddata[0]['body'];
1273
                            $itemdata['extract'] = $this->prepare_search_result_for_display($ddata[0]['body']);
1274
                            $itemdata['person_id'] = $ddata[0]['person_id'];
1275
                            if ($itemdata['person_id']) {
1276
                                $itemdata['speaker'] = $this->_get_speaker($itemdata['person_id'], $itemdata['hdate'], $itemdata['htime'], $itemdata['major']);
1277
                            }
1278
                        }
1279
                    } elseif ($itemdata['htype'] == 10) {
1280
                        $itemdata['body'] = '';
1281
                        $itemdata['extract'] = '';
1282
                    }
1283
                }
1284
1285
            } // End of handling non-calendar search result
1286
1287
            $rows[] = $itemdata;
1288
        }
1289
1290
        $data['rows'] = $rows;
1291
        return $data;
1292
    }
1293
1294
    public function prepare_search_result_for_display($body) {
1295
        global $SEARCHENGINE;
1296
        // We want to trim the body to an extract that is centered
1297
        // around the position of the first search word.
1298
1299
        // we don't use strip_tags as it doesn't replace tags with spaces,
1300
        // which means some words end up stuck together
1301
        $extract = strip_tags_tospaces($body);
1302
1303
        // $bestpos is the position of the first search word
1304
        $bestpos = $SEARCHENGINE->position_of_first_word($extract);
1305
1306
        // Where do we want to extract from the $body to start?
1307
        $length_of_extract = 400; // characters.
1308
        $startpos = $bestpos - ($length_of_extract / 2);
1309
        if ($startpos < 0) {
1310
            $startpos = 0;
1311
        }
1312
1313
        // Trim it to length and position, adding ellipses.
1314
        // Decode HTML entities so position matches up.
1315
        $extract = trim_characters(html_entity_decode($extract), $startpos, $length_of_extract);
1316
1317
        // Highlight search words
1318
        $extract = mb_encode_numericentity(htmlentities($extract, ENT_QUOTES, 'UTF-8'), [0x80, 0x10FFFF, 0, ~0], 'UTF-8');
1319
        $extract = $SEARCHENGINE->highlight($extract);
1320
1321
        return $extract;
1322
    }
1323
1324
    public function _get_data_by_calendar($args) {
1325
        // We should have come here via _get_data_by_calendar() in
1326
        // DEBATELIST or WRANLIST, so $this->major should now be set.
1327
1328
        // You can ask for:
1329
        // * The most recent n months - $args['months'] => n
1330
        // * All months from one year - $args['year'] => 2004
1331
        // * One month - $args['year'] => 2004, $args['month'] => 8
1332
        // * The months from this year so far (no $args variables needed).
1333
1334
        // $args['onday'] may be like '2004-04-20' - if it appears in the
1335
        // calendar, this date will be highlighted and will have no link.
1336
1337
        // Returns a data structure of years, months and dates:
1338
        // $data = array(
1339
        // 		'info' => array (
1340
        //			'page' => 'debates',
1341
        //			'major'	=> 1
1342
        //			'onpage' => '2004-02-01'
1343
        //		),
1344
        // 		'years' => array (
1345
        //			'2004' => array (
1346
        //				'01' => array ('01', '02', '03' ... '31'),
1347
        //				'02' => etc...
1348
        //			)
1349
        //		)
1350
        // )
1351
        // It will just have entries for days for which we have relevant
1352
        // hansard data.
1353
        // But months that have no data will still have a month array (empty).
1354
1355
        // $data['info'] may have 'prevlink' => '/debates/?y=2003' or something
1356
        // if we're viewing recent months.
1357
1358
        global $DATA, $this_page, $PAGE, $hansardmajors;
1359
1360
        // What we return.
1361
        $data = [
1362
            'info' => [
1363
                'page' => $this->listpage,
1364
                'major' => $this->major,
1365
            ],
1366
        ];
1367
1368
        // Set a variable so we know what we're displaying...
1369
        if (isset($args['months']) && is_numeric($args['months'])) {
1370
1371
            // A number of recent months (may wrap around to previous year).
1372
            $action = 'recentmonths';
1373
1374
            // A check to prevent anyone requestion 500000 months.
1375
            if ($args['months'] > 12) {
1376
                $PAGE->error_message("Sorry, you can't view " . $args['months'] . " months.");
1377
                return $data;
1378
            }
1379
1380
        } elseif (isset($args['year']) && is_numeric($args['year'])) {
1381
1382
            if (isset($args['month']) && is_numeric($args['month'])) {
1383
                // A particular month.
1384
                $action = 'month';
1385
            } else {
1386
                // A single year.
1387
                $action = 'year';
1388
            }
1389
1390
        } else {
1391
            // The year to date so far.
1392
            $action = 'recentyear';
1393
        }
1394
1395
        if (isset($args['onday'])) {
1396
            // Will be highlighted.
1397
            $data['info']['onday'] = $args['onday'];
1398
        }
1399
1400
        // This first if/else section is simply to fill out these variables:
1401
1402
        if ($action == 'recentmonths' || $action == 'recentyear') {
1403
1404
            // We're either getting the most recent $args['months'] data
1405
            // Or the most recent year's data.
1406
            // (Not necessarily recent to *now* but compared to the most
1407
            // recent date for which we have relevant hansard data.)
1408
            // 'recentyear' will include all the months that haven't happened yet.
1409
1410
            // Find the most recent date we have data for.
1411
            $q = $this->db->query(
1412
                "SELECT MAX(hdate) AS hdate
1413
                            FROM	hansard
1414
                            WHERE	major = :major",
1415
                [':major' => $this->major]
1416
            )->first();
1417
1418
            if ($q && $q['hdate'] != null) {
1419
                $recentdate = $q['hdate'];
1420
            } else {
1421
                $PAGE->error_message("Couldn't find the most recent date");
1422
                return $data;
1423
            }
1424
1425
            // What's the first date of data we need to fetch?
1426
            [$finalyear, $finalmonth, $day] = explode('-', $recentdate);
1427
1428
            $finalyear = intval($finalyear);
1429
            $finalmonth = intval($finalmonth);
1430
1431
            if ($action == 'recentmonths') {
1432
1433
                // We're getting this many recent months.
1434
                $months_to_fetch = $args['months'];
1435
1436
                // The month we need to start getting data.
1437
                $firstmonth = intval($finalmonth) - $months_to_fetch + 1;
1438
1439
                $firstyear = $finalyear;
1440
1441
                if ($firstmonth < 1) {
1442
                    // Wrap round to previous year.
1443
                    $firstyear--;
1444
                    // $firstmonth is negative, hence the '+'.
1445
                    $firstmonth = 12 + $firstmonth; // ()
1446
                };
1447
1448
            } else {
1449
                // $action == 'recentyear'
1450
1451
                // Get the most recent year's results.
1452
                $firstyear = $finalyear;
1453
                $firstmonth = 1;
1454
            }
1455
1456
1457
1458
        } else {
1459
            // $action == 'year' or 'month'.
1460
1461
            $firstyear = $args['year'];
1462
            $finalyear = $args['year'];
1463
1464
            if ($action == 'month') {
1465
                $firstmonth = intval($args['month']);
1466
                $finalmonth = intval($args['month']);
1467
            } else {
1468
                $firstmonth = 1;
1469
                $finalmonth = 12;
1470
            }
1471
1472
            $params = [
1473
                ':firstdate' => $firstyear . '-' . $firstmonth . '-01',
1474
                ':finaldate' => $finalyear . '-' . $finalmonth . '-31'];
1475
1476
            // Check there are some dates for this year/month.
1477
            $q = $this->db->query("SELECT epobject_id
1478
                            FROM	hansard
1479
                            WHERE	hdate >= :firstdate
1480
                            AND 	hdate <= :finaldate
1481
                            LIMIT 	1
1482
                            ", $params);
1483
1484
            if ($q->rows() == 0) {
1485
                // No data in db, so return empty array!
1486
                return $data;
1487
            }
1488
1489
        }
1490
1491
        // OK, Now we have $firstyear, $firstmonth, $finalyear, $finalmonth set up.
1492
1493
        // Get the data...
1494
1495
        $where = '';
1496
        $params = [];
1497
1498
        if ($finalyear > $firstyear || $finalmonth >= $firstmonth) {
1499
            $params[':finaldate'] = $finalyear . '-' . $finalmonth . '-31';
1500
            $where = 'AND hdate <= :finaldate';
1501
        }
1502
1503
        $params[':major'] = $this->major;
1504
        $params[':firstdate'] = $firstyear . '-' . $firstmonth . '-01';
1505
        $q =  $this->db->query("SELECT 	DISTINCT(hdate) AS hdate
1506
                        FROM		hansard
1507
                        WHERE		major = :major
1508
                        AND			hdate >= :firstdate
1509
                        $where
1510
                        ORDER BY	hdate ASC
1511
                        ", $params);
1512
1513
        if ($q->rows() > 0) {
1514
1515
            // We put the data in this array. See top of function for the structure.
1516
            $years = [];
1517
1518
            foreach ($q as $row) {
1519
                [$year, $month, $day] = explode('-', $row['hdate']);
1520
1521
                $month = intval($month);
1522
                $day = intval($day);
1523
1524
                // Add as a link.
1525
                $years[$year][$month][] = $day;
1526
            }
1527
1528
            // If nothing happened on one month we'll have fetched nothing for it.
1529
            // So now we need to fill in any gaps with blank months.
1530
1531
            // We cycle through every year and month we're supposed to have fetched.
1532
            // If it doesn't have an array in $years, we create an empty one for that
1533
            // month.
1534
            for ($y = $firstyear; $y <= $finalyear; $y++) {
1535
1536
                if (!isset($years[$y])) {
1537
                    $years[$y] = [1 => [], 2 => [], 3 => [], 4 => [], 5 => [], 6 => [], 7 => [], 8 => [], 9 => [], 10 => [], 11 => [], 12 => []];
1538
                } else {
1539
1540
                    // This year is set. Check it has all the months...
1541
1542
                    $minmonth = $y == $firstyear ? $firstmonth : 1;
1543
                    $maxmonth = $y == $finalyear ? $finalmonth : 12;
1544
1545
                    for ($m = $minmonth; $m <= $maxmonth; $m++) {
1546
                        if (!isset($years[$y][$m])) {
1547
                            $years[$y][$m] = [];
1548
                        }
1549
                    }
1550
                    ksort($years[$y]);
1551
1552
                }
1553
            }
1554
1555
            $data['years'] = $years;
1556
        }
1557
1558
        // Set the next/prev links.
1559
1560
        $YEARURL = new \MySociety\TheyWorkForYou\Url($hansardmajors[$this->major]['page_year']);
1561
1562
        if (substr($this_page, -4) == 'year') {
1563
            // Only need next/prev on these pages.
1564
            // Not sure this is the best place for this, but...
1565
1566
            $nextprev = [];
1567
1568
            if ($action == 'recentyear') {
1569
                // Assuming there will be a previous year!
1570
1571
                $YEARURL->insert(['y' => $firstyear - 1]);
1572
1573
                $nextprev['prev'] =  [
1574
                    'body' => 'Previous year',
1575
                    'title' => $firstyear - 1,
1576
                    'url' => $YEARURL->generate(),
1577
                ];
1578
1579
            } else {
1580
                // action is 'year'.
1581
1582
                $nextprev['prev'] =  ['body' => 'Previous year'];
1583
                $nextprev['next'] =  ['body' => 'Next year'];
1584
1585
                $q = $this->db->query("SELECT DATE_FORMAT(hdate, '%Y') AS year
1586
                            FROM hansard WHERE major = :major
1587
                            AND year(hdate) < :firstyear
1588
                            ORDER BY hdate DESC
1589
                            LIMIT 1", [
1590
                    ':major' => $this->major,
1591
                    ':firstyear' => $firstyear,
1592
                ])->first();
1593
                if ($action == 'year' && $q) {
1594
                    $YEARURL->insert(['y' => $q['year']]);
1595
                    $nextprev['prev']['title'] = $q['year'];
1596
                    $nextprev['prev']['url'] = $YEARURL->generate();
1597
                }
1598
1599
                $q = $this->db->query("SELECT DATE_FORMAT(hdate, '%Y') AS year
1600
                            FROM hansard WHERE major = :major
1601
                            AND year(hdate) > :finalyear
1602
                            ORDER BY hdate
1603
                            LIMIT 1", [
1604
                    ':major' => $this->major,
1605
                    ':finalyear' => $finalyear,
1606
                ])->first();
1607
                if ($q) {
1608
                    $YEARURL->insert(['y' => $q['year']]);
1609
                    $nextprev['next']['title'] = $q['year'];
1610
                    $nextprev['next']['url'] = $YEARURL->generate();
1611
                }
1612
            }
1613
1614
            // Will be used in $PAGE.
1615
            $DATA->set_page_metadata($this_page, 'nextprev', $nextprev);
1616
        }
1617
1618
        return $data;
1619
1620
    }
1621
1622
    public function _get_mentions($spid) {
1623
        $q = $this->db->query("select gid, type, date, url, mentioned_gid
1624
            from mentions where gid like 'uk.org.publicwhip/spq/$spid'
1625
            order by date, type");
1626 3
        $result = $q->fetchAll();
1627 3
        return $result;
1628
    }
1629
1630
    protected function _get_hansard_data($input) {
1631
        global $hansardmajors, $MEMBER;
1632
1633
        // Generic function for getting hansard data from the DB.
1634
        // It returns an empty array if no data was found.
1635
        // It returns an array of items if 1 or more were found.
1636
        // Each item is an array of key/value pairs.
1637
        // eg:
1638
        /*
1639
            array (
1640
                0	=> array (
1641
                    'epobject_id'	=> '2',
1642
                    'htype'			=> '10',
1643
                    'section_id'		=> '0',
1644
                    etc...
1645
                ),
1646
                1	=> array (
1647
                    'epobject_id'	=> '3',
1648
                    etc...
1649
                )
1650
            );
1651
        */
1652
1653
        // $input['amount'] is an associative array indicating what data should be fetched.
1654
        // It has the structure
1655
        // 	'key' => true
1656
        // Where 'true' indicates the data of type 'key' should be fetched.
1657
        // Leaving a key/value pair out is the same as setting a key to false.
1658
1659
        // $input['amount'] can have any or all these keys:
1660
        //	'body' 		- Get the body text from the epobject table.
1661
        //	'comment' 	- Get the first comment (and totalcomments count) for this item.
1662
        //	'votes'		- Get the user votes for this item.
1663
        //	'speaker'	- Get the speaker for this item, where applicable.
1664
        //  'excerpt' 	- For sub/sections get the body text for the first item within them.
1665
1666
        // $input['wherearr'] is an associative array of stuff for the WHERE clause, eg:
1667 3
        // 	array ('id=' => '37', 'date>' => '2003-12-31');
1668 3
        // $input['order'] is a string for the $order clause, eg 'hpos DESC'.
1669 3
        // $input['limit'] as a string for the $limit clause, eg '21,20'.
1670 3
1671
        $amount 		= $input['amount'] ?? [];
1672
        $wherearr 		= $input['where'] ?? [];
1673
        $order 			= $input['order'] ?? '';
1674
        $limit 			= $input['limit'] ?? '';
1675 3
1676
1677
        // The fields to fetch from db. 'table' => array ('field1', 'field2').
1678 3
        $fieldsarr =  [
1679
            'hansard' =>  ['epobject_id', 'htype', 'gid', 'hpos', 'section_id', 'subsection_id', 'hdate', 'htime', 'source_url', 'major', 'minor', 'colnum'],
1680 3
        ];
1681 2
1682
        $params = [];
1683
1684 3
        if (isset($amount['speaker']) && $amount['speaker'] == true) {
1685 3
            $fieldsarr['hansard'][] = 'person_id';
1686
        }
1687 3
1688 3
        if ((isset($amount['body']) && $amount['body'] == true) ||
1689
            (isset($amount['comment']) && $amount['comment'] == true)
1690
        ) {
1691
            $fieldsarr['epobject'] =  ['body'];
1692
            $join = 'LEFT OUTER JOIN epobject ON hansard.epobject_id = epobject.epobject_id';
1693
        } else {
1694 3
            $join = '';
1695
        }
1696 3
1697 3
1698 3
        $fieldsarr2 =  [];
1699
        // Construct the $fields clause.
1700
        foreach ($fieldsarr as $table => $tablesfields) {
1701 3
            foreach ($tablesfields as $n => $field) {
1702
                $fieldsarr2[] = $table . '.' . $field;
1703 3
            }
1704
        }
1705 3
        $fields = implode(', ', $fieldsarr2);
1706 3
1707 3
        $wherearr2 =  [];
1708 3
        // Construct the $where clause.
1709 3
        $i = 0;
1710
        foreach ($wherearr as $key => $val) {
1711 3
            $params[":where$i"] = $val;
1712
            $wherearr2[] = "$key :where$i";
1713
            $i++;
1714 3
        }
1715
        $where = implode(" AND ", $wherearr2);
1716 2
1717
1718 2
        if ($order != '') {
1719
            # You can't use parameters for order by clauses
1720
            $order_by_clause = "ORDER BY $order";
1721 3
        } else {
1722 1
            $order_by_clause = '';
1723 1
        }
1724
1725 3
        if ($limit != '') {
1726
            $params[':limit'] = $limit;
1727
            $limit = "LIMIT :limit";
1728
        } else {
1729 3
            $limit = '';
1730
        }
1731 3
1732 3
        // Finally, do the query!
1733 3
        $q = $this->db->query("SELECT $fields
1734 3
                        FROM 	hansard
1735
                        $join
1736
                        WHERE $where
1737
                        $order_by_clause
1738 3
                        $limit
1739 3
                        ", $params);
1740
1741
        // Format the data into an array for returning.
1742 3
        $data = [];
1743
        foreach ($q as $row) {
1744
            // Where we'll store the data for this item before adding
1745 3
            // it to $data.
1746 3
            $item = [];
1747 3
1748
            // Put each row returned into its own array in $data.
1749
            foreach ($fieldsarr as $table => $tablesfields) {
1750
                foreach ($tablesfields as $m => $field) {
1751 3
                    $item[$field] = $row[$field];
1752
                }
1753
            }
1754 3
1755
            if (isset($item['gid'])) {
1756
                // Remove the "uk.org.publicwhip/blah/" from the gid:
1757
                // (In includes/utility.php)
1758
                $item['gid'] = fix_gid_from_db($item['gid']);
1759
            }
1760 3
1761
            // Add mentions if (a) it's a question in the written
1762
            // answer section or (b) it's in the official reports
1763
            // and the body text ends in a bracketed SPID.
1764
            if (($this->major && $hansardmajors[$this->major]['page'] == 'spwrans') && ($item['htype'] == '12' && $item['minor'] == '1')) {
1765
                // Get out the SPID:
1766
                if (preg_match('#\d{4}-\d\d-\d\d\.(.*?)\.q#', $item['gid'], $m)) {
1767
                    $item['mentions'] = $this->_get_mentions($m[1]);
1768 3
                }
1769
            }
1770
1771
            // The second case (b):
1772
            if (($this->major && $hansardmajors[$this->major]['page'] == 'spdebates') && isset($item['body'])) {
1773
                $stripped_body = preg_replace('/<[^>]+>/ms', '', $item['body']);
1774
                if (preg_match('/\((S\d+\w+-\d+)\)/ms', $stripped_body, $m)) {
1775 3
                    $item['mentions'] = $this->_get_mentions($m[1]);
1776
                }
1777
            }
1778
1779
            if (in_array($item['epobject_id'], [15674958, 15674959, 12822764, 12822765, 27802084, 27802037])) {
1780
                global $DATA, $this_page;
1781
                $DATA->set_page_metadata($this_page, 'robots', 'noindex');
1782
            }
1783 3
1784
            // Get the number of items within a section or subsection.
1785 3
            // It could be that we can do this in the main query?
1786
            // Not sure.
1787
            if (($this->major && $hansardmajors[$this->major]['type'] == 'debate') && ($item['htype'] == '10' || $item['htype'] == '11')) {
1788 3
1789 3
                if ($item['htype'] == '10') {
1790
                    // Section - get a count of items within this section that
1791
                    // don't have a subsection heading.
1792
                    $where = "section_id = '" . $item['epobject_id'] . "'
1793 1
                        AND subsection_id = '" . $item['epobject_id'] . "'";
1794
1795
                } else {
1796 3
                    // Subsection - get a count of items within this subsection.
1797
                    $where = "subsection_id = '" . $item['epobject_id'] . "'";
1798 3
                }
1799
1800 3
                $r = $this->db->query("SELECT COUNT(*) AS count
1801
                                FROM 	hansard
1802 3
                                WHERE 	$where
1803 3
                                AND htype = 12
1804
                                ")->first();
1805
1806
                if ($r) {
1807
                    $item['contentcount'] = $r['count'];
1808
                } else {
1809
                    $item['contentcount'] = '0';
1810
                }
1811
            }
1812
1813 3
            // Get the body of the first item with the section or
1814 1
            // subsection. This can then be printed as an excerpt
1815 3
            // on the daily list pages.
1816
1817 1
            if ((isset($amount['excerpt']) && $amount['excerpt'] == true) &&
1818 1
                ($item['htype'] == '10' ||
1819 1
                $item['htype'] == '11')
1820
            ) {
1821
                $params = [':epobject_id' => $item['epobject_id']];
1822
                if ($item['htype'] == '10') {
1823
                    $where = 'hansard.section_id = :epobject_id
1824
                        AND hansard.subsection_id = :epobject_id';
1825 1
                } elseif ($item['htype'] == '11') {
1826
                    $where = 'hansard.subsection_id = :epobject_id';
1827
                }
1828 1
1829
                $r = $this->db->query("SELECT epobject.body
1830
                                FROM 	hansard,
1831 1
                                        epobject
1832
                                WHERE	$where
1833 1
                                AND		hansard.epobject_id = epobject.epobject_id
1834
                                ORDER BY hansard.hpos ASC
1835
                                LIMIT	1", $params)->first();
1836
1837
                if ($r) {
1838 3
                    $item['excerpt'] = $r['body'];
1839
                }
1840
            }
1841
1842
            if ($item['htype'] == 14) {
1843
                $divisions = new MySociety\TheyWorkForYou\Divisions();
1844
                $division_votes = $divisions->getDivisionByGid($this->gidprefix . $item['gid']);
1845
                $item['division'] = $division_votes;
1846
                # Don't want MP vote on PBC pages
1847
                if (isset($MEMBER) && $this->major != 6) {
1848
                    $item['mp_vote'] = $divisions->getDivisionResultsForMember($division_votes['division_id'], $MEMBER->person_id());
1849
                    if (!$item['mp_vote']) {
1850
                        if ($division_votes['date'] < $MEMBER->entered_house($division_votes['house_number'])['date']) {
1851
                            $item['before_mp'] = true;
1852
                        } elseif ($division_votes['date'] > $MEMBER->left_house($division_votes['house_number'])['date']) {
1853
                            $item['after_mp'] = true;
1854
                        }
1855
                    }
1856
                }
1857
            }
1858
1859
1860
            // We generate two permalinks for each item:
1861
            // 'listurl' is the URL of the item in the full list view.
1862 3
            // 'commentsurl' is the URL of the item on its own page, with comments.
1863 3
1864 3
            // All the things we need to work out a listurl!
1865 3
            $item_data =  [
1866 3
                'major'			=> $this->major,
1867 3
                'minor' 		=> $item['minor'],
1868
                'htype' 		=> $item['htype'],
1869
                'gid' 			=> $item['gid'],
1870
                'section_id'	=> $item['section_id'],
1871 3
                'subsection_id'	=> $item['subsection_id'],
1872
            ];
1873
1874
1875 3
            $item['listurl'] = $this->_get_listurl($item_data);
1876
1877
1878
            // Create a URL for where we can see all the comments for this item.
1879
            if (isset($this->commentspage)) {
1880
                $COMMENTSURL = new \MySociety\TheyWorkForYou\Url($this->commentspage);
1881
                if ($this->major == 6) {
1882
                    # Another hack...
1883
                    $COMMENTSURL->remove(['id']);
1884
                    $id = preg_replace('#^.*?_.*?_#', '', $item['gid']);
1885
                    $fragment = $this->url . $id;
1886
                    $item['commentsurl'] = $COMMENTSURL->generate() . $fragment;
1887
                } else {
1888
                    $COMMENTSURL->insert(['id' => $item['gid']]);
1889
                    $item['commentsurl'] = $COMMENTSURL->generate();
1890 3
                }
1891 3
            }
1892
1893
            // Get the user/anon votes items that have them.
1894
            if (($this->major == 3 || $this->major == 8) && (isset($amount['votes']) && $amount['votes'] == true) &&
1895
                $item['htype'] == '12') {
1896
                // Debate speech or written answers (not questions).
1897
1898 3
                $item['votes'] = $this->_get_votes($item['epobject_id']);
1899 3
            }
1900
1901 2
            // Get the speaker for this item, if applicable.
1902
            if ((isset($amount['speaker']) && $amount['speaker'] == true) &&
1903
                $item['person_id'] != '') {
1904
1905
                $item['speaker'] = $this->_get_speaker($item['person_id'], $item['hdate'], $item['htime'], $item['major']);
1906 3
            }
1907
1908
1909
            // Get comment count and (if any) most recent comment for each item.
1910 3
            if (isset($amount['comment']) && $amount['comment'] == true) {
1911 3
1912
                // All the things we need to get the comment data.
1913
                $item_data =  [
1914 3
                    'htype' => $item['htype'],
1915 3
                    'epobject_id' => $item['epobject_id'],
1916 3
                ];
1917
1918
                $commentdata = $this->_get_comment($item_data);
1919
                $item['totalcomments'] = $commentdata['totalcomments'];
1920
                $item['comment'] = $commentdata['comment'];
1921 3
            }
1922
1923
1924 3
            // Add this item on to the array of items we're returning.
1925
            $data[] = $item;
1926
        }
1927
1928
        return $data;
1929
    }
1930
1931
1932
    public function _get_votes($epobject_id) {
1933
        // Called from _get_hansard_data().
1934
        // Separated out here just for clarity.
1935
        // Returns an array of user and anon yes/no votes for an epobject.
1936
1937
        $votes = [];
1938
1939
        // YES user votes.
1940
        $q = $this->db->query("SELECT COUNT(vote) as totalvotes
1941
                        FROM	uservotes
1942
                        WHERE	epobject_id = :epobject_id
1943
                        AND 	vote = '1'
1944
                        GROUP BY epobject_id", [':epobject_id' => $epobject_id])->first();
1945
1946
        if ($q) {
1947
            $votes['user']['yes'] = $q['totalvotes'];
1948
        } else {
1949
            $votes['user']['yes'] = '0';
1950
        }
1951
1952
        // NO user votes.
1953
        $q = $this->db->query("SELECT COUNT(vote) as totalvotes
1954
                        FROM	uservotes
1955
                        WHERE	epobject_id = :epobject_id
1956
                        AND 	vote = '0'
1957
                        GROUP BY epobject_id", [':epobject_id' => $epobject_id])->first();
1958
1959
        if ($q) {
1960
            $votes['user']['no'] = $q['totalvotes'];
1961
        } else {
1962
            $votes['user']['no'] = '0';
1963
        }
1964
1965
1966
        // Get the anon votes for each item.
1967
1968
        $q = $this->db->query(
1969
            "SELECT yes_votes,
1970
                                no_votes
1971
                        FROM	anonvotes
1972
                        WHERE	epobject_id = :epobject_id",
1973
            [':epobject_id' => $epobject_id]
1974
        )->first();
1975
1976
        if ($q) {
1977
            $votes['anon']['yes'] = $q['yes_votes'];
1978
            $votes['anon']['no'] = $q['no_votes'];
1979
        } else {
1980
            $votes['anon']['yes'] = '0';
1981
            $votes['anon']['no'] = '0';
1982 4
        }
1983 4
1984
        return $votes;
1985
    }
1986
1987
1988
    public function _get_listurl($id_data, $url_args = [], $encode = 'html') {
1989
        global $hansardmajors;
1990
        // Generates an item's listurl - this is the 'contextual' url
1991
        // for an item, in the full list view with an anchor (if appropriate).
1992
1993
        // $id_data is like this:
1994
        //		$id_data = array (
1995
        //		'major' 		=> 1,
1996
        //		'htype' 		=> 12,
1997 4
        //		'gid' 			=> 2003-10-30.421.4h2,
1998 4
        //		'section_id'	=> 345,
1999
        //		'subsection_id'	=> 346
2000
        // );
2001
2002
        // $url_args is an array of other key/value pairs to be appended in the GET string.
2003 4
        if ($id_data['major']) {
2004
            $LISTURL = new \MySociety\TheyWorkForYou\Url($hansardmajors[$id_data['major']]['page_all']);
2005 4
        } else {
2006 4
            $LISTURL = new \MySociety\TheyWorkForYou\Url('wrans');
2007
        }
2008
2009
        # From search results we are called as a bare HANSARDLIST
2010
        # so do not have $this->url available to us.
2011
        if ($id_data['major'] == 6) {
2012 4
            global $DATA;
2013
            $minor = $id_data['minor'];
2014
            if (isset($this->bill_lookup[$minor])) {
2015
                [$title, $session] = $this->bill_lookup[$minor];
2016
            } else {
2017
                $qq = $this->db->query('select title, session from bills where id=' . $minor)->first();
2018
                $title = $qq['title'];
2019
                $session = $qq['session'];
2020
                $this->bill_lookup[$minor] = [$title, $session];
2021
            }
2022
            $title = str_replace(' ', '_', $title);
2023
            $pbc_url = 'pbc/' . urlencode($session) . '/' . urlencode($title);
2024
        }
2025
2026
        $fragment = '';
2027
2028
        if ($id_data['htype'] == '11' || $id_data['htype'] == '10') {
2029
            if ($id_data['major'] == 6) {
2030
                $id = preg_replace('#^.*?_.*?_#', '', $id_data['gid']);
2031
                $DATA->set_page_metadata('pbc_clause', 'url', "$pbc_url/$id");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $DATA does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $pbc_url does not seem to be defined for all execution paths leading up to this point.
Loading history...
2032
                $LISTURL->remove(['id']);
2033
            } else {
2034
                $LISTURL->insert([ 'id' => $id_data['gid'] ]);
2035
            }
2036
        } else {
2037
            // A debate speech or question/answer, etc.
2038
            // We need to get the gid of the parent (sub)section for this item.
2039
            // We use this with the gid of the item itself as an #anchor.
2040
2041
            $parent_epobject_id = $id_data['subsection_id'];
2042
2043
            // Find the gid of this item's (sub)section.
2044
            $parent_gid = '';
2045
2046
            if (isset($this->epobjectid_to_gid[ $parent_epobject_id ])) {
2047
                // We've previously cached the gid for this epobject_id, so use that.
2048
2049
                $parent_gid = $this->epobjectid_to_gid[ $parent_epobject_id ];
2050
2051
            } else {
2052
                // We haven't cached the gid, so fetch from db.
2053
2054
                $r = $this->db->query(
2055
                    "SELECT gid
2056
                                FROM 	hansard
2057
                                WHERE	epobject_id = :epobject_id",
2058
                    [':epobject_id' => $parent_epobject_id]
2059
                )->first();
2060
2061
                if ($r) {
2062
                    // Remove the "uk.org.publicwhip/blah/" from the gid:
2063
                    // (In includes/utility.php)
2064
                    $parent_gid = fix_gid_from_db($r['gid']);
2065
2066
                    // Cache it for if we need it again:
2067
                    $this->epobjectid_to_gid[ $parent_epobject_id ] = $parent_gid;
2068
                }
2069
            }
2070
2071
            if ($parent_gid != '') {
2072 4
                // We have a gid so add to the URL.
2073 1
                if ($id_data['major'] == 6) {
2074
                    $parent_gid = preg_replace('#^.*?_.*?_#', '', $parent_gid);
2075
                    $DATA->set_page_metadata('pbc_clause', 'url', "$pbc_url/$parent_gid");
2076 4
                    $LISTURL->remove(['id']);
2077
                } else {
2078
                    $LISTURL->insert([ 'id' => $parent_gid ]);
2079 3
                }
2080 3
                // Use a truncated form of this item's gid for the anchor.
2081 1
                $fragment = '#g' . gid_to_anchor($id_data['gid']);
2082
            }
2083
        }
2084
2085
        if (count($url_args) > 0) {
2086 3
            $LISTURL->insert($url_args);
2087 1
        }
2088
2089
        return $LISTURL->generate($encode) . $fragment;
2090
    }
2091
2092 3
    public function _get_speaker($person_id, $hdate, $htime, $major) {
2093
        if ($person_id == 0) {
2094 3
            return [];
2095
        }
2096 3
2097
        # Special exemption below for when NI Speaker becomes Speaker
2098 3
        # mid-debate, so we don't want to cache
2099
        if (isset($this->speakers["$person_id-$hdate"]) && !($person_id == 13831 && $hdate == '2015-01-12')) {
2100 3
            return $this->speakers["$person_id-$hdate"];
2101
        }
2102 3
2103
        # Special exemptions for people 'speaking' after they have left
2104
        $hdate_month = substr($hdate, 0, 7);
2105
        $hdate = $this->after_left["$person_id,$hdate"] ?? $hdate;
2106
        $hdate = $this->after_left["$person_id,$hdate_month"] ?? $hdate;
2107 3
2108
        # London questions answered after election
2109
        if ($major == 9 && ($hdate == '2021-05-11' || $hdate == '2021-05-10')) {
2110 3
            $hdate = '2021-05-07';
2111
        }
2112
2113
        # check for a person redirect
2114
        $q = $this->db->query(
2115 3
            "SELECT gid_to FROM gidredirect
2116
                WHERE gid_from = :gid_from",
2117 3
            [':gid_from' => "uk.org.publicwhip/person/$person_id"]
2118 3
        )->first();
2119 3
        if ($q) {
2120
            $person_id = str_replace('uk.org.publicwhip/person/', '', $q['gid_to']);
2121
        }
2122
2123 3
        $q = $this->db->query(
2124
            "SELECT title, given_name, family_name, lordofname,
2125
                                house,
2126
                                constituency,
2127
                                party,
2128
                                member_id
2129
                        FROM    member m, person_names p
2130
                        WHERE	m.person_id = :person_id
2131
                            AND entered_house <= :hdate AND :hdate <= left_house
2132
                            AND p.person_id = m.person_id AND p.type = 'name'
2133
                            AND p.start_date <= :hdate AND :hdate <= p.end_date
2134 3
                        ORDER BY entered_house",
2135 3
            [':person_id' => $person_id, ':hdate' => $hdate]
2136
        );
2137 3
        $member = $this->_get_speaker_alone($q, $person_id, $hdate, $htime, $major);
2138 3
2139
        $URL = $this->_get_speaker_url($member['house']);
2140 3
        $URL->insert(['p' => $person_id]);
2141
2142 3
        $name = member_full_name($member['house'], $member['title'], $member['given_name'], $member['family_name'], $member['lordofname']);
2143 3
        $speaker =  [
2144 3
            'member_id' => $member['member_id'],
2145 3
            "name" => $name,
2146 3
            'house' => $member['house'],
2147 3
            "constituency" => $member["constituency"] ? gettext($member["constituency"]) : '',
2148 3
            "party" => $member["party"] ? gettext($member["party"]) : '',
2149
            "person_id" => $person_id,
2150
            "url" => $URL->generate(),
2151 3
        ];
2152
2153 3
        global $parties;
2154
        // Manual fix for Speakers.
2155
        if (isset($parties[$speaker['party']])) {
2156
            $speaker['party'] = $parties[$speaker['party']];
2157 3
        }
2158 3
2159 3
        $speaker['office'] = $this->_get_speaker_offices($speaker, $hdate);
2160
        $this->speakers["$person_id-$hdate"] = $speaker;
2161
        return $speaker;
2162 3
    }
2163 3
2164 3
    private function _get_speaker_alone($q, $person_id, $hdate, $htime, $major) {
2165 2
        $members = $q->fetchAll();
2166 2
        if (count($members) > 1) {
2167 2
            $members = array_filter($members, function ($m) use ($major) {
2168 2
                $houses = \MySociety\TheyWorkForYou\Utility\House::majorToHouse($major);
2169
                return in_array($m['house'], $houses);
2170 2
            });
2171
            # Of course, remember PHP treats lists as dictionaries
2172
            $members = array_values($members);
2173 3
        }
2174
        # Note identical code to this in search/index.pl
2175
        if (count($members) > 1) {
2176
            # Couple of special cases for the election of the NI Speaker
2177
            if ($person_id == 13799 && $hdate == '2007-05-08') {
2178
                $members = [$members[$htime < '11:00' ? 0 : 1]];
2179
            } elseif ($person_id == 13831 && $hdate == '2015-01-12') {
2180
                $members = [$members[$htime < '13:00' ? 0 : 1]];
2181 3
            }
2182
        }
2183
        if (count($members) != 1) {
2184
            throw new \Exception('Wanted one result, but got ' . count($members) . " for $person_id, $hdate, $major.");
2185 3
        }
2186
2187
        return $members[0];
2188 3
    }
2189 3
2190 3
    private function _get_speaker_url($house) {
2191
        $URL = new \MySociety\TheyWorkForYou\Url('mp'); # Default, house=1
2192 3
        if ($house == HOUSE_TYPE_LORDS) {
2193
            $URL = new \MySociety\TheyWorkForYou\Url('peer');
2194 3
        } elseif ($house == HOUSE_TYPE_NI) {
2195
            $URL = new \MySociety\TheyWorkForYou\Url('mla');
2196 3
        } elseif ($house == HOUSE_TYPE_SCOTLAND) {
2197
            $URL = new \MySociety\TheyWorkForYou\Url('msp');
2198 3
        } elseif ($house == HOUSE_TYPE_WALES) {
2199
            $URL = new \MySociety\TheyWorkForYou\Url('ms');
2200
        } elseif ($house == HOUSE_TYPE_ROYAL) {
2201 3
            $URL = new \MySociety\TheyWorkForYou\Url('royal');
2202
        }
2203
        return $URL;
2204 3
    }
2205 3
2206 3
    private function _get_speaker_offices($speaker, $hdate) {
2207
        $offices = [];
2208
        $q = $this->db->query(
2209 3
            "SELECT dept, position, source FROM moffice
2210 3
            WHERE person=:person_id
2211
            AND from_date <= :hdate and :hdate <= to_date",
2212
            [':person_id' => $speaker['person_id'], ':hdate' => $hdate]
2213
        );
2214
        foreach ($q as $row) {
2215
            $dept = $row['dept'];
2216
            $pos = $row['position'];
2217
            $source = $row['source'];
2218
            if ($source == 'chgpages/libdem' && $hdate > '2009-01-15') {
2219
                continue;
2220
            }
2221
            if (!$pos || $pos == 'Chairman' || $pos == 'Member') {
2222
                continue;
2223
            }
2224
            $offices[] = [
2225
                'dept' => $dept,
2226
                'position' => $pos,
2227 3
                'source' => $source,
2228
                'pretty' => prettify_office($pos, $dept),
2229
            ];
2230 3
        }
2231
        return $offices;
2232
    }
2233
2234
    public function _get_comment($item_data) {
2235
        // Pass it some variables belonging to an item and the function
2236
        // returns an array containing:
2237
        // 1) A count of the comments within this item.
2238
        // 2) The details of the most recent comment posted to this item.
2239
2240 3
        // Sections/subsections have (1) (the sum of the comments
2241 3
        // of all contained items), but not (2).
2242
2243 3
        // What we return.
2244
        $totalcomments = $this->_get_comment_count_for_epobject($item_data);
2245
        $comment = [];
2246
2247
        if ($item_data['htype'] == '12' || $item_data['htype'] == '13' || $item_data['htype'] == '14') {
2248
2249
            // Things which can have comments posted directly to them.
2250
2251
            if ($totalcomments > 0) {
2252
2253
                // Get the first comment.
2254
2255
                // Not doing this for Wrans sections because we don't
2256
                // need it anywhere. Arbitrary but it'll save us MySQL time!
2257
2258
                $q = $this->db->query(
2259
                    "SELECT c.comment_id,
2260
                                    c.user_id,
2261
                                    c.body,
2262
                                    c.posted,
2263
                                    u.firstname,
2264
                                    u.lastname
2265
                            FROM	comments c, users u
2266
                            WHERE	c.epobject_id = :epobject_id
2267
                            AND		c.user_id = u.user_id
2268
                            AND		c.visible = 1
2269
                            ORDER BY c.posted ASC
2270
                            LIMIT	1",
2271
                    [':epobject_id' => $item_data['epobject_id']]
2272
                )->first();
2273
2274
                // Add this comment to the data structure.
2275
                $comment =  [
2276
                    'comment_id' => $q['comment_id'],
2277
                    'user_id'	=> $q['user_id'],
2278
                    'body'		=> $q['body'],
2279
                    'posted'	=> $q['posted'],
2280
                    'username'	=> $q['firstname'] . ' ' . $q['lastname'],
2281
                ];
2282
            }
2283
2284
        }
2285 3
2286 3
        // We don't currently allow people to post comments to a section
2287
        // or subsection itself, only the items within them. So
2288
        // we don't get the most recent comment. Because there isn't one.
2289 3
2290
        $return =  [
2291
            'totalcomments' => $totalcomments,
2292
            'comment' => $comment,
2293 3
        ];
2294 3
2295
        return $return;
2296
    }
2297
2298 3
2299 3
    public function _get_comment_count_for_epobject($item_data) {
2300
        global $hansardmajors;
2301
        // What it says on the tin.
2302
        // $item_data must have 'htype' and 'epobject_id' elements. TODO: Check for major==4
2303 3
2304 3
        if (($hansardmajors[$this->major]['type'] == 'debate') &&
2305
            ($item_data['htype'] == '10' || $item_data['htype'] == '11')
2306
        ) {
2307 3
            // We'll be getting a count of the comments on all items
2308
            // within this (sub)section.
2309
            $from = "comments, hansard";
2310 3
            $where = "comments.epobject_id = hansard.epobject_id
2311
                    AND subsection_id = :epobject_id";
2312
2313
            if ($item_data['htype'] == '10') {
2314
                // Section - get a count of comments within this section that
2315
                // don't have a subsection heading.
2316
                $where .= " AND section_id = :epobject_id";
2317
            }
2318
2319 3
        } else {
2320 3
            // Just getting a count of the comments on this item.
2321 3
            $from = "comments";
2322
            $where = 'epobject_id = :epobject_id';
2323 3
        }
2324
2325 3
        $q = $this->db->query(
2326
            "SELECT COUNT(*) AS count
2327
                        FROM 	$from
2328 1
                        WHERE	$where
2329
                        AND		visible = 1",
2330
            [':epobject_id' => $item_data['epobject_id']]
2331
        )->first();
2332 1
2333
        return $q['count'];
2334 1
    }
2335
2336
    public function _get_data_by_gid($args) {
2337 1
2338 1
        // We need to get the data for this gid.
2339
        // Then depending on what htype it is, we get the data for other items too.
2340
        global $DATA, $this_page, $hansardmajors;
2341
2342
        twfy_debug(get_class($this), "getting data by gid");
2343 1
2344
        // Get the information about the item this URL refers to.
2345
        $itemdata = $this->_get_item($args);
2346
        if (!$itemdata) {
2347
            return [];
2348
        }
2349
2350
        // If part of a Written Answer (just question or just answer), select the whole thing
2351
        if (isset($itemdata['major']) && $hansardmajors[$itemdata['major']]['type'] == 'other' and ($itemdata['htype'] == '12' or $itemdata['htype'] == '13' or $itemdata['htype'] == '14')) {
2352
            // find the gid of the subheading which holds this part
2353
            $input =  [
2354
                'amount' => ['gid' => true],
2355
                'where' =>  [
2356
                    'epobject_id=' => $itemdata['subsection_id'],
2357
                ],
2358
            ];
2359
            $parent = $this->_get_hansard_data($input);
2360 1
            // display that item, i.e. the whole of the Written Answer
2361
            twfy_debug(get_class($this), "instead of " . $args['gid'] . " selecting subheading gid " . $parent[0]['gid'] . " to get whole wrans");
2362
            $args['gid'] = $parent[0]['gid'];
2363
            $this->_get_item($args);
2364
            throw new RedirectException($args['gid']);
2365
        }
2366
2367
        # If a WMS main heading, go to next gid
2368
        if (isset($itemdata['major']) && $itemdata['major'] == 4 && $itemdata['htype'] == '10') {
2369
            $input =  [
2370
                'amount' => ['gid' => true],
2371
                'where' => [
2372
                    'section_id=' => $itemdata['epobject_id'],
2373
                ],
2374
                'order' => 'hpos ASC',
2375
                'limit' => 1,
2376
            ];
2377
            $next = $this->_get_hansard_data($input);
2378
            if (!empty($next)) {
2379 1
                twfy_debug(get_class($this), 'instead of ' . $args['gid'] . ' moving to ' . $next[0]['gid']);
2380
                $args['gid'] = $next[0]['gid'];
2381 1
                $this->_get_item($args);
2382 1
                throw new RedirectException($args['gid']);
2383 1
            }
2384
        }
2385
2386 1
        // Where we'll put all the data we want to render.
2387
        $data = [];
2388
2389 1
        if (isset($itemdata['htype'])) {
2390 1
            $this->htype = $itemdata['htype'];
2391
            if ($this->htype >= 12) {
2392 1
                $this_page = $this->commentspage;
2393 1
            } else {
2394
                $this_page = $this->listpage;
2395
            }
2396
        }
2397 1
        if (isset($itemdata['epobject_id'])) {
2398 1
            $this->epobject_id = $itemdata['epobject_id'];
2399 1
        }
2400
        if (isset($itemdata['gid'])) {
2401
            $this->gid = $itemdata['gid'];
2402
        }
2403 1
2404
        // We'll use these for page headings/titles:
2405
        $data['info']['date'] = $itemdata['hdate'];
2406 1
        $data['info']['text'] = $itemdata['body'];
2407
        $data['info']['major'] = $this->major;
2408
2409
        // If we have a member id we'll pass it on to the template so it
2410 1
        // can highlight all their speeches.
2411
        if (isset($args['member_id'])) {
2412
            $data['info']['member_id'] = $args['member_id'];
2413
        }
2414
        if (isset($args['person_id'])) {
2415
            $data['info']['person_id'] = $args['person_id'];
2416
        }
2417 1
2418 1
        if (isset($args['s']) && $args['s'] != '') {
2419
            // We have some search term words that we could highlight
2420
            // when rendering.
2421
            $data['info']['searchstring'] = $args['s'];
2422 1
        }
2423 1
2424 1
        // Get the section and subsection headings for this item.
2425
        $sectionrow = $this->_get_section($itemdata);
2426
        $subsectionrow = $this->_get_subsection($itemdata);
2427
2428
        // Get the nextprev links for this item, to link to next/prev pages.
2429
        // Duh.
2430
        if ($itemdata['htype'] == '10') {
2431
            $nextprev = $this->_get_nextprev_items($sectionrow);
2432
            $data['info']['text_heading'] = $itemdata['body'];
2433
2434
        } elseif ($itemdata['htype'] == '11') {
2435
            $nextprev = $this->_get_nextprev_items($subsectionrow);
2436
            $data['info']['text_heading'] = $itemdata['body'];
2437
2438
        } else {
2439
            // Ordinary lowly item.
2440
            $nextprev = $this->_get_nextprev_items($itemdata);
2441
2442
            if (isset($subsectionrow['gid'])) {
2443
                $nextprev['up']['url'] 		= $subsectionrow['listurl'];
2444
                $nextprev['up']['title'] 	= $subsectionrow['body'];
2445 1
            } else {
2446
                $nextprev['up']['url'] 		= $sectionrow['listurl'];
2447
                $nextprev['up']['title'] 	= $sectionrow['body'];
2448
            }
2449
            $nextprev['up']['body']		= gettext('See the whole debate');
2450
        }
2451 1
2452
        // We can then access this from $PAGE and the templates.
2453
        $DATA->set_page_metadata($this_page, 'nextprev', $nextprev);
2454
2455
        // Now get all the non-heading rows.
2456
2457 1
        // What data do we want for each item?
2458
        $amount =  [
2459
            'body' => true,
2460
            'speaker' => true,
2461
            'comment' => true,
2462 1
            'votes' => true,
2463
        ];
2464 1
2465 1
        if ($itemdata['htype'] == '10') {
2466
            // This item is a section, so we're displaying all the items within
2467 1
            // it that aren't within a subsection.
2468
2469
            $input =  [
2470 1
                'amount' => $amount,
2471 1
                'where' =>  [
2472
                    'section_id=' => $itemdata['epobject_id'],
2473
                    'subsection_id=' => $itemdata['epobject_id'],
2474
                ],
2475 1
                'order' => 'hpos ASC',
2476
            ];
2477
2478
            $data['rows'] = $this->_get_hansard_data($input);
2479
            if (!count($data['rows']) || (count($data['rows']) == 1 && strstr($data['rows'][0]['body'], 'was asked'))) {
2480 1
2481 1
                $input =  [
2482 1
                    'amount' =>  [
2483
                        'body' => true,
2484 1
                        'comment' => true,
2485
                        'excerpt' => true,
2486 1
                    ],
2487
                    'where' =>  [
2488 1
                        'section_id='	=> $sectionrow['epobject_id'],
2489 1
                        'htype='		=> '11',
2490
                        'major='		=> $this->major,
2491
                    ],
2492
                    'order' => 'hpos',
2493
                ];
2494
                $data['subrows'] = $this->_get_hansard_data($input);
2495
                # If there's only one subheading, and nothing in the heading, redirect to it immediaetly
2496
                if (count($data['subrows']) == 1) {
2497
                    throw new RedirectException($data['subrows'][0]['gid']);
2498
                }
2499
            }
2500
        } elseif ($itemdata['htype'] == '11') {
2501
            // This item is a subsection, so we're displaying everything within it.
2502
2503
            $input =  [
2504
                'amount' => $amount,
2505
                'where' =>  [
2506
                    'subsection_id=' => $itemdata['epobject_id'],
2507
                ],
2508
                'order' => 'hpos ASC',
2509
            ];
2510
2511
            $data['rows'] = $this->_get_hansard_data($input);
2512
2513
2514 1
        } elseif ($itemdata['htype'] == '12' || $itemdata['htype'] == '13' || $itemdata['htype'] == '14') {
2515 1
            // Debate speech or procedural, so we're just displaying this one item.
2516
2517
            $data['rows'][] = $itemdata;
2518
2519
        }
2520
2521 1
        // Put the section and subsection at the top of the rows array.
2522
        if (count($subsectionrow) > 0 &&
2523 1
            $subsectionrow['gid'] != $sectionrow['gid']) {
2524
            // If we're looking at a section, there may not be a subsection.
2525
            // And if the subsectionrow and sectionrow aren't the same.
2526
            array_unshift($data['rows'], $subsectionrow);
2527
        }
2528
2529
        array_unshift($data['rows'], $sectionrow);
2530
2531
        return $data;
2532
2533
    }
2534
2535
    public function _get_data_by_column($args) {
2536
        global $this_page;
2537
2538
        twfy_debug(get_class($this), "getting data by column");
2539
2540
        $input = [ 'amount' => ['body' => true, 'comment' => true, 'speaker' => true],
2541
            'where' => [ 'hdate=' => $args['date'], 'major=' => $this->major, 'gid LIKE ' => '%.' . $args['column'] . '.%' ],
2542
            'order' => 'hpos',
2543
        ];
2544
        $data = $this->_get_hansard_data($input);
2545
        #		$data = array();
2546
2547
        #		$itemdata = $this->_get_item($args);
2548
2549
        #		if ($itemdata) {
2550
        #	$data['info']['date'] = $itemdata['hdate'];
2551
        #			$data['info']['text'] = $itemdata['body'];
2552
        #			$data['info']['major'] = $this->major;
2553
        #		}
2554
        return $data;
2555
    }
2556
2557
}
2558
2559
class WMSLIST extends WRANSLIST {
2560
    public $major = 4;
2561
    public $listpage = 'wms';
2562
    public $commentspage = 'wms';
2563
    public $gidprefix = 'uk.org.publicwhip/wms/';
2564
2565
    public function _get_data_by_recent_wms($args = []) {
2566
        return $this->_get_data_by_recent_wrans($args);
2567
    }
2568
}
2569
2570
class WHALLLIST extends DEBATELIST {
2571
    public $major = 2;
2572
    public $listpage = 'whalls';
2573
    public $commentspage = 'whall';
2574
    public $gidprefix = 'uk.org.publicwhip/westminhall/';
2575
}
2576
2577
class NILIST extends DEBATELIST {
2578
    public $major = 5;
2579
    public $listpage = 'nidebates';
2580
    public $commentspage = 'nidebate';
2581
    public $gidprefix = 'uk.org.publicwhip/ni/';
2582
}
2583
2584
class SENEDDENLIST extends DEBATELIST {
2585
    public $major = 10;
2586
    public $listpage = 'senedddebates';
2587
    public $commentspage = 'senedddebate';
2588
    public $gidprefix = 'uk.org.publicwhip/senedd/en/';
2589
}
2590
2591
class SENEDDCYLIST extends DEBATELIST {
2592
    public $major = 11;
2593
    public $listpage = 'senedddebates';
2594
    public $commentspage = 'senedddebate';
2595
    public $gidprefix = 'uk.org.publicwhip/senedd/cy/';
2596
}
2597
2598
class LMQLIST extends WRANSLIST {
2599
    public $major = 9;
2600
    public $listpage = 'lmqs';
2601
    public $commentspage = 'lmqs';
2602
    public $gidprefix = 'uk.org.publicwhip/london-mayors-questions/';
2603
}
2604
2605
class SPLIST extends DEBATELIST {
2606
    public $major = 7;
2607
    public $listpage = 'spdebates';
2608
    public $commentspage = 'spdebate';
2609
    public $gidprefix = 'uk.org.publicwhip/spor/';
2610
}
2611
2612
class SPWRANSLIST extends WRANSLIST {
2613
    public $major = 8;
2614
    public $listpage = 'spwrans';
2615
    public $commentspage = 'spwrans';
2616
    public $gidprefix = 'uk.org.publicwhip/spwa/';
2617
2618
    public function get_gid_from_spid($spid) {
2619
        // Fix the common errors of S.0 instead of S.O and leading
2620
        // zeros in the numbers:
2621
        $fixed_spid = preg_replace('/(S[0-9]+)0-([0-9]+)/', '${1}O-${2}', $spid);
2622
        $fixed_spid = preg_replace('/(S[0-9]+\w+)-0*([0-9]+)/', '${1}-${2}', $fixed_spid);
2623
        $q = $this->db->query(
2624
            "select mentioned_gid from mentions where gid = :gid_from_spid and (type = 4 or type = 6)",
2625
            [':gid_from_spid' => 'uk.org.publicwhip/spq/' . $fixed_spid]
2626
        )->first();
2627
        $gid = $q['mentioned_gid'];
2628
        if ($gid) {
2629
            return $gid;
2630
        }
2631
        return null;
2632
    }
2633
    public function old_get_gid_from_spid($spid) {
2634
        $q = $this->db->query(
2635
            "select gid from hansard where gid like :gid_like",
2636
            [':gid_like' => 'uk.org.publicwhip/spwa/%.' . $spid . '.h']
2637
        )->first();
2638
        $gid = $q['gid'];
2639
        if ($gid) {
2640
            return str_replace('uk.org.publicwhip/spwa/', '', $gid);
2641
        }
2642
        return null;
2643
    }
2644
}
2645
2646
class LORDSDEBATELIST extends DEBATELIST {
2647
    public $major = 101;
2648
    public $listpage = 'lordsdebates';
2649
    public $commentspage = 'lordsdebate';
2650
    public $gidprefix = 'uk.org.publicwhip/lords/';
2651
}
2652
2653
class DEBATELIST extends HANSARDLIST {
2654
    public $major = 1;
2655
    public $listpage = 'debates';
2656
    public $commentspage = 'debate';
2657
    public $gidprefix = 'uk.org.publicwhip/debate/';
2658
2659
    public function _get_data_by_recent_mostvotes($args) {
2660
        // Get the most highly voted recent speeches.
2661
        // $args may have 'days'=>7 and/or 'num'=>5
2662
        // or something like that.
2663
2664
        // The most voted on things during how many recent days?
2665
        if (isset($args['days']) && is_numeric($args['days'])) {
2666
            $days = $args['days'];
2667
        } else {
2668
            $days = 7;
2669
        }
2670
2671
        // How many results?
2672
        if (isset($args['num']) && is_numeric($args['num'])) {
2673
            $items_to_list = $args['num'];
2674
        } else {
2675
            $items_to_list = 5;
2676
        }
2677
2678
        $q = $this->db->query("SELECT min(subsection_id) AS subsection_id,
2679
                                min(section_id) AS section_id,
2680
                                min(htype) AS htype,
2681
                                min(gid) AS gid,
2682
                                min(major) AS major, min(minor) AS minor,
2683
                                min(hdate) AS hdate, min(htime) AS htime,
2684
                                min(person_id) AS person_id,
2685
                                min(epobject.body) AS body,
2686
                                SUM(uservotes.vote) + anonvotes.yes_votes AS total_vote
2687
                        FROM	hansard,
2688
                                epobject
2689
                                LEFT OUTER JOIN uservotes ON epobject.epobject_id = uservotes.epobject_id
2690
                                LEFT OUTER JOIN anonvotes ON epobject.epobject_id = anonvotes.epobject_id
2691
                        WHERE		major = :major
2692
                        AND		hansard.epobject_id = epobject.epobject_id
2693
                        AND		hdate >= DATE_SUB(CURDATE(), INTERVAL $days DAY)
2694
                        GROUP BY epobject.epobject_id
2695
                        HAVING 	total_vote > 0
2696
                        ORDER BY total_vote DESC
2697
                        LIMIT	$items_to_list
2698
                        ", [':major' => $this->major]);
2699
2700
        // What we return.
2701
        $data =  [];
2702
        $speeches = [];
2703
        foreach ($q as $row) {
2704
            $speech =  [
2705
                'subsection_id' => $row['subsection_id'],
2706
                'section_id' => $row['section_id'],
2707
                'htype' => $row['htype'],
2708
                'major' => $row['major'],
2709
                'minor' => $row['minor'],
2710
                'hdate' => $row['hdate'],
2711
                'body' => $row['body'],
2712
                'votes' => $row['total_vote'],
2713
            ];
2714
2715
            // Remove the "uk.org.publicwhip/blah/" from the gid:
2716
            // (In includes/utility.php)
2717
            $speech['gid'] = fix_gid_from_db($row['gid']);
2718
            $speech['listurl'] = $this->_get_listurl($speech);
2719
            $speech['speaker'] = $this->_get_speaker($row['person_id'], $row['hdate'], $row['htime'], $this->major);
2720
            $speeches[] = $speech;
2721
        }
2722
2723
        if (count($speeches) > 0) {
2724
            // Get the subsection texts.
2725
2726
            $num_speeches = count($speeches);
2727
            for ($n = 0; $n < $num_speeches; $n++) {
2728
                //if ($this->major == 1) {
2729
                // Debate.
2730
                $parent = $this->_get_subsection($speeches[$n]);
2731
2732
                //} elseif ($this->major == 3) {
2733
                // Wrans.
2734
                //	$parent = $this->_get_section ($speeches[$n]);
2735
                //}
2736
                // Add the parent's body on...
2737
                //if (isset($parent['body'])) {
2738
                $speeches[$n]['parent']['body'] = $parent['body'];
2739
                //} else {
2740
                //	$parent = $this->_get_section ($speeches[$n]);
2741
                //	$speeches[$n]['parent']['body'] = $parent['body'];
2742
                //}
2743
2744
            }
2745
2746
            $data['rows'] = $speeches;
2747
2748
        } else {
2749
            $data['rows'] =  [];
2750
        }
2751
2752
        $data['info']['days'] = $days;
2753
2754
        return $data;
2755
    }
2756
2757
2758
    public function total_speeches() {
2759
2760
        $q = $this->db->query("SELECT COUNT(*) AS count FROM hansard WHERE major = :major AND htype = 12", [':major' => $this->major]);
2761
2762
        return $q->first()['count'];
2763
    }
2764
2765
2766
    public function biggest_debates($args = []) {
2767
        // So we can just get the data back for special formatting
2768
        // on the front page, without doing the whole display() thing.
2769
        return $this->_get_data_by_biggest_debates($args);
2770
    }
2771
2772
    public function _get_data_by_featured_gid($args = []) {
2773
        $params = [];
2774
        $data = [];
2775
2776
        $params[':gid'] = $args['gid'];
2777
        $params[':major'] = $this->major;
2778
2779
        $query = "SELECT
2780
                    body,
2781
                    title,
2782
                    h.hdate,
2783
                    h.htime,
2784
                    h.htype,
2785
                    h.minor,
2786
                    h.gid,
2787
                    h.person_id,
2788
                    h.subsection_id,
2789
                    h.section_id,
2790
                    h.epobject_id
2791
            FROM    hansard h, epobject e
2792
            WHERE   h.major = :major
2793
            AND     h.gid = :gid
2794
            AND     h.epobject_id = e.epobject_id";
2795
2796
        $q = $this->db->query($query, $params)->first();
2797
2798
        if ($q) {
2799
2800
            // This array just used for getting further data about this debate.
2801
            $item_data =  [
2802
                'major'         => $this->major,
2803
                'minor'         => $q['minor'],
2804
                'gid'           => fix_gid_from_db($q['gid']),
2805
                'htype'         => $q['htype'],
2806
                'section_id'    => $q['section_id'],
2807
                'subsection_id' => $q['subsection_id'],
2808
                'epobject_id'   => $q['epobject_id'],
2809
            ];
2810
2811
            $list_url      = $this->_get_listurl($item_data);
2812
            $totalcomments = $this->_get_comment_count_for_epobject($item_data);
2813
2814
            $body          = $q['body'];
2815
            $hdate         = $q['hdate'];
2816
            $htime         = $q['htime'];
2817
2818
            // If this is a subsection, we're going to prepend the title
2819
            // of the parent section, so let's get that.
2820
            $parentbody = '';
2821
            if ($item_data['htype'] == 12) {
2822
                $r = $this->db->query(
2823
                    "SELECT sec.body as sec_body, sub.body as sub_bod
2824
                                FROM    epobject sec, epobject sub
2825
                                WHERE   sec.epobject_id = :section_id
2826
                                AND     sub.epobject_id = :subsection_id",
2827
                    [
2828
                        ':section_id' => $item_data['section_id'],
2829
                        ':subsection_id' => $item_data['subsection_id'],
2830
                    ]
2831
                )->first();
2832
                $section_body = $r['sec_body'];
2833
                $subsection_body = $r['sub_body'];
2834
                if ($section_body != $subsection_body) {
2835
                    $parentbody = "$section_body : $subsection_body";
2836
                } else {
2837
                    $parentbody = $section_body;
2838
                }
2839
            } elseif ($item_data['htype'] == 11) {
2840
                $r = $this->db->query(
2841
                    "SELECT body FROM epobject WHERE epobject_id = :section_id",
2842
                    [
2843
                        ':section_id' => $item_data['section_id'],
2844
                    ]
2845
                )->first();
2846
                $parentbody = $r['body'];
2847
            } elseif ($item_data['htype'] == 10) {
2848
                $parentbody = $body;
2849
            }
2850
2851
            // Get the question for this item.
2852
            if ($item_data['htype'] == 12) {
2853
                $childbody = $body;
2854
                $speaker = $this->_get_speaker($q['person_id'], $q['hdate'], $q['htime'], $this->major);
2855
            } else {
2856
                $r = $this->db->query(
2857
                    "SELECT e.body, e.title,
2858
                                        h.person_id, h.hdate, h.htime
2859
                                FROM    hansard h, epobject e
2860
                                WHERE   h.epobject_id = e.epobject_id
2861
                                AND     h.subsection_id = :object_id
2862
                                ORDER BY hpos
2863
                                LIMIT 1
2864
                                ",
2865
                    [ ':object_id' => $item_data['epobject_id'] ]
2866
                )->first();
2867
                $childbody = $r['body'];
2868
                $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
2869
            }
2870
2871
            $contentcount = 0;
2872
            $r = $this->db->query(
2873
                "SELECT COUNT(*) AS count
2874
                            FROM hansard
2875
                            WHERE subsection_id = :object_id
2876
                            AND htype = 12",
2877
                [':object_id' => $item_data['epobject_id']]
2878
            )->first();
2879
2880
            if ($r) {
2881
                $contentcount = $r['count'];
2882
            }
2883
2884
            global $hansardmajors;
2885
            $more_url = new \MySociety\TheyWorkForYou\Url($hansardmajors[$this->major]['page_all']);
2886
            $details = [
2887
                'body'          => $body,
2888
                'contentcount'  => $contentcount,
2889
                'hdate'         => $hdate,
2890
                'htime'         => $htime,
2891
                'list_url'      => $list_url,
2892
                'totalcomments' => $totalcomments,
2893
                'child'         => [
2894
                    'body'      => $childbody,
2895
                    'speaker'   => $speaker,
2896
                ],
2897
                'parent'        => [
2898
                    'body'      => $parentbody,
2899
                ],
2900
                'desc' => $hansardmajors[$this->major]['title'],
2901
                'more_url' => $more_url->generate(),
2902
            ];
2903
2904
            $data =  [
2905
                'gid' => $args['gid'],
2906
                'major' => $this->major,
2907
                'info' => [],
2908
                'data' => $details,
2909
            ];
2910
        }
2911
2912
        return $data;
2913
2914
    }
2915
    public function _get_data_by_recent_debates($args = []) {
2916
        // Returns an array of some random recent debates from a set number of
2917
        // recent days (that's recent days starting from the most recent day
2918
        // that had any debates on).
2919
2920
        // $args['days'] is the number of days back to look for biggest debates (1 by default).
2921
        // $args['num'] is the number of links to return (1 by default).
2922
2923
        $data = [];
2924
2925
        $params = [];
2926
2927
        // Get the most recent day on which we have a debate.
2928
        $recentday = $this->most_recent_day();
2929
        if (!count($recentday)) {
2930
            return $data;
2931
        }
2932
2933
        if (!isset($args['days']) || !is_numeric($args['days'])) {
2934
            $args['days'] = 1;
2935
        }
2936
        if (!isset($args['num']) || !is_numeric($args['num'])) {
2937
            $args['num'] = 1;
2938
        }
2939
2940
        if ($args['num'] == 1) {
2941
            $datewhere = "h.hdate = :hdate";
2942
            $params[':hdate'] = $recentday['hdate'];
2943
        } else {
2944
            $firstdate = gmdate('Y-m-d', $recentday['timestamp'] - (86400 * $args['days']));
2945
            $datewhere = "h.hdate >= :firstdate
2946
                        AND h.hdate <= :hdate";
2947
            $params[':firstdate'] = $firstdate;
2948
            $params[':hdate'] = $recentday['hdate'];
2949
        }
2950
2951
        $params[':limit'] = $args['num'];
2952
        $params[':major'] = $this->major;
2953
2954
        $query = "SELECT COUNT(*) AS count,
2955
                    min(body) AS body,
2956
                    min(h.hdate) AS hdate,
2957
                    min(sech.htype) AS htype,
2958
                    min(sech.htime) AS htime,
2959
                    min(sech.gid) AS gid,
2960
                    min(sech.subsection_id) AS subsection_id,
2961
                    min(sech.section_id) AS section_id,
2962
                    min(sech.epobject_id) AS epobject_id
2963
            FROM    hansard h, epobject e, hansard sech
2964
            WHERE   h.major = :major
2965
            AND     $datewhere
2966
            AND     h.subsection_id = e.epobject_id
2967
            AND     sech.epobject_id = h.subsection_id
2968
            GROUP BY h.subsection_id
2969
            HAVING  count >= 5
2970
            ORDER BY RAND()
2971
            LIMIT   :limit";
2972
2973
        $q = $this->db->query($query, $params);
2974
        foreach ($q as $row) {
2975
2976
            // This array just used for getting further data about this debate.
2977
            $item_data =  [
2978
                'major' => $this->major,
2979
                'gid' => fix_gid_from_db($row['gid']),
2980
                'htype' => $row['htype'],
2981
                'section_id' => $row['section_id'],
2982
                'subsection_id' => $row['subsection_id'],
2983
                'epobject_id' => $row['epobject_id'],
2984
            ];
2985
2986
            $list_url      = $this->_get_listurl($item_data);
2987
            $totalcomments = $this->_get_comment_count_for_epobject($item_data);
2988
2989
            $contentcount  = $row['count'];
2990
            $body          = $row['body'];
2991
            $hdate         = $row['hdate'];
2992
            $htime         = $row['htime'];
2993
2994
            // If this is a subsection, we're going to prepend the title
2995
            // of the parent section, so let's get that.
2996
            $parentbody = '';
2997
            if ($item_data['htype'] == 11) {
2998
                $r = $this->db->query(
2999
                    "SELECT body
3000
                                FROM    epobject
3001
                                WHERE   epobject_id = :epobject_id",
3002
                    [':epobject_id' => $item_data['section_id']]
3003
                )->first();
3004
                $parentbody = $r['body'];
3005
            }
3006
3007
            // Get the question for this item.
3008
            $r = $this->db->query("SELECT e.body,
3009
                                    h.person_id, h.hdate, h.htime
3010
                            FROM    hansard h, epobject e
3011
                            WHERE   h.epobject_id = e.epobject_id
3012
                            AND     h.subsection_id = '" . $item_data['epobject_id'] . "'
3013
                            ORDER BY hpos
3014
                            LIMIT 1
3015
                            ")->first();
3016
            $childbody = $r['body'];
3017
            $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
3018
3019
            $data[] = [
3020
                'contentcount'  => $contentcount,
3021
                'body'          => $body,
3022
                'hdate'         => $hdate,
3023
                'htime'         => $htime,
3024
                'list_url'      => $list_url,
3025
                'totalcomments' => $totalcomments,
3026
                'child'         => [
3027
                    'body'      => $childbody,
3028
                    'speaker'   => $speaker,
3029
                ],
3030
                'parent'        => [
3031
                    'body'      => $parentbody,
3032
                ],
3033
            ];
3034
3035
        }
3036
3037
        $data =  [
3038
            'info' => [],
3039
            'data' => $data,
3040
        ];
3041
3042
        return $data;
3043
3044
    }
3045
3046
    public function _get_data_by_biggest_debates($args = []) {
3047
        // Returns an array of the debates with most speeches in from
3048
        // a set number of recent days (that's recent days starting from the
3049
        // most recent day that had any debates on).
3050
3051
        // $args['days'] is the number of days back to look for biggest debates.
3052
        // (1 by default)
3053
        // $args['num'] is the number of links to return (1 by default).
3054
3055
        $data = [];
3056
3057
        // Get the most recent day on which we have a debate.
3058
        $recentday = $this->most_recent_day();
3059
        if (!count($recentday)) {
3060
            return [];
3061
        }
3062
3063
        if (!isset($args['days']) || !is_numeric($args['days'])) {
3064
            $args['days'] = 1;
3065
        }
3066
        if (!isset($args['num']) || !is_numeric($args['num'])) {
3067
            $args['num'] = 1;
3068
        }
3069
3070
        $params = [':recentdate' => $recentday['hdate']];
3071
        if ($args['num'] == 1) {
3072
            $datewhere = "h.hdate = :recentdate";
3073
        } else {
3074
            $params[':firstdate'] = gmdate('Y-m-d', $recentday['timestamp'] - (86400 * $args['days']));
3075
            $datewhere = "h.hdate >= :firstdate AND	h.hdate <= :recentdate";
3076
        }
3077
3078
        $params[':limit'] = $args['num'];
3079
        $params[':major'] = $this->major;
3080
3081
        $q = $this->db->query("SELECT COUNT(*) AS count,
3082
                                min(body) AS body,
3083
                                min(h.hdate) AS hdate,
3084
                                min(sech.htype) AS htype,
3085
                                min(sech.gid) AS gid,
3086
                                min(sech.subsection_id) AS subsection_id,
3087
                                min(sech.section_id) AS section_id,
3088
                                min(sech.epobject_id) AS epobject_id
3089
                        FROM 	hansard h, epobject e, hansard sech
3090
                        WHERE 	h.major = :major
3091
                        AND 	$datewhere
3092
                        AND  	h.subsection_id = e.epobject_id
3093
                        AND 	sech.epobject_id = h.subsection_id
3094
                        GROUP BY h.subsection_id
3095
                        ORDER BY count DESC
3096
                        LIMIT :limit", $params);
3097
3098
        foreach ($q as $row) {
3099
3100
            // This array just used for getting further data about this debate.
3101
            $item_data =  [
3102
                'major' => $this->major,
3103
                'gid' => fix_gid_from_db($row['gid']),
3104
                'htype' => $row['htype'],
3105
                'section_id' => $row['section_id'],
3106
                'subsection_id' => $row['subsection_id'],
3107
                'epobject_id' => $row['epobject_id'],
3108
            ];
3109
3110
            $list_url 		= $this->_get_listurl($item_data);
3111
            $totalcomments	= $this->_get_comment_count_for_epobject($item_data);
3112
3113
            $contentcount = $row['count'];
3114
            $body = $row['body'];
3115
            $hdate = $row['hdate'];
3116
3117
3118
            // This array will be added to $data, which is what gets returned.
3119
            $debate =  [
3120
                'contentcount'	=> $contentcount,
3121
                'body'			=> $body,
3122
                'hdate'			=> $hdate,
3123
                'list_url'		=> $list_url,
3124
                'totalcomments'	=> $totalcomments,
3125
            ];
3126
3127
            // If this is a subsection, we're going to prepend the title
3128
            // of the parent section, so let's get that.
3129
            if ($item_data['htype'] == 11) {
3130
3131
                $r = $this->db->query(
3132
                    "SELECT body
3133
                                FROM	epobject
3134
                                WHERE	epobject_id = :epobject_id",
3135
                    [':epobject_id' => $item_data['section_id']]
3136
                )->first();
3137
                $debate['parent']['body'] = $r['body'];
3138
            }
3139
3140
            $r = $this->db->query("SELECT e.body,
3141
                                    h.person_id, h.hdate, h.htime
3142
                            FROM    hansard h, epobject e
3143
                            WHERE   h.epobject_id = e.epobject_id
3144
                            AND     h.subsection_id = '" . $item_data['epobject_id'] . "'
3145
                            ORDER BY hpos
3146
                            LIMIT 1
3147
                            ")->first();
3148
            $childbody = $r['body'];
3149
            $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
3150
3151
            $debate['child'] = [
3152
                'body' => $childbody,
3153
                'speaker' => $speaker,
3154
            ];
3155
3156
            $data[] = $debate;
3157
        }
3158
3159
        $data =  [
3160
            'info' => [],
3161
            'data' => $data,
3162
        ];
3163
3164
        return $data;
3165
3166
    }
3167
3168
}
3169
3170
3171
class WRANSLIST extends HANSARDLIST {
3172
    public $major = 3;
3173
    public $listpage = 'wrans';
3174
    public $commentspage = 'wrans'; // We don't have a separate page for wrans comments.
3175
    public $gidprefix = 'uk.org.publicwhip/wrans/';
3176
3177
    public function total_questions() {
3178
        $q = $this->db->query("SELECT COUNT(*) AS count FROM hansard WHERE major = :major AND minor = 1", [':major' => $this->major]);
3179
        return $q->first()['count'];
3180
    }
3181
3182
    public function _get_data_by_recent_wrans($args = []) {
3183
        global $hansardmajors;
3184
3185
        // $args['days'] is the number of days back to look for biggest debates.
3186
        // (1 by default)
3187
        // $args['num'] is the number of links to return (1 by default).
3188
3189
        $data = [];
3190
3191
        $params = [];
3192
3193
        // Get the most recent day on which we have wrans.
3194
        $recentday = $this->most_recent_day();
3195
        if (!count($recentday)) {
3196
            return $data;
3197
        }
3198
3199
        if (!isset($args['days']) || !is_numeric($args['days'])) {
3200
            $args['days'] = 1;
3201
        }
3202
        if (!isset($args['num']) || !is_numeric($args['num'])) {
3203
            $args['num'] = 1;
3204
        }
3205
3206
        if ($args['num'] == 1) {
3207
            $datewhere = "h.hdate = :datewhere";
3208
            $params[':datewhere'] = $recentday['hdate'];
3209
        } else {
3210
            $firstdate = gmdate('Y-m-d', $recentday['timestamp'] - (86400 * $args['days']));
3211
            $datewhere = "h.hdate >= :firstdate AND h.hdate <= :hdate";
3212
            $params[':firstdate'] = $firstdate;
3213
            $params[':hdate'] = $recentday['hdate'];
3214
        }
3215
3216
3217
        // Get a random selection of subsections in wrans.
3218
        if (in_array($hansardmajors[$this->major]['location'], ['Scotland', 'London'])) {
3219
            $htype = 'htype = 10 and section_id = 0';
3220
        } else {
3221
            $htype = 'htype = 11 and section_id != 0';
3222
        }
3223
3224
        $params[':limit'] = $args['num'];
3225
        $params[':major'] = $this->major;
3226
3227
        $query = "SELECT e.body,
3228
                    h.hdate,
3229
                    h.htype,
3230
                    h.gid,
3231
                    h.subsection_id,
3232
                    h.section_id,
3233
                    h.epobject_id
3234
            FROM    hansard h, epobject e
3235
            WHERE   h.major = :major
3236
            AND     $htype
3237
            AND     subsection_id = 0
3238
            AND     $datewhere
3239
            AND     h.epobject_id = e.epobject_id
3240
            ORDER BY RAND()
3241
            LIMIT   :limit";
3242
3243
        $q = $this->db->query($query, $params);
3244
3245
        foreach ($q as $row) {
3246
            // This array just used for getting further data about this debate.
3247
            $item_data =  [
3248
                'major' => $this->major,
3249
                'gid' => fix_gid_from_db($row['gid']),
3250
                'htype' => $row['htype'],
3251
                'section_id' => $row['section_id'],
3252
                'subsection_id' => $row['subsection_id'],
3253
                'epobject_id' => $row['epobject_id'],
3254
            ];
3255
3256
            $list_url 		= $this->_get_listurl($item_data);
3257
            $totalcomments	= $this->_get_comment_count_for_epobject($item_data);
3258
3259
            $body = $row['body'];
3260
            $hdate = $row['hdate'];
3261
3262
            // Get the parent section for this item.
3263
            $parentbody = '';
3264
            if ($row['section_id']) {
3265
                $r = $this->db->query("SELECT e.body
3266
                            FROM	hansard h, epobject e
3267
                            WHERE	h.epobject_id = e.epobject_id
3268
                            AND		h.epobject_id = '" . $row['section_id'] . "'
3269
                            ")->first();
3270
                $parentbody = $r['body'];
3271
            }
3272
3273
            // Get the question for this item.
3274
            $r = $this->db->query("SELECT e.body,
3275
                                    h.person_id, h.hdate, h.htime
3276
                            FROM	hansard h, epobject e
3277
                            WHERE	h.epobject_id = e.epobject_id
3278
                            AND 	h.subsection_id = '" . $row['epobject_id'] . "'
3279
                            ORDER BY hpos
3280
                            LIMIT 1
3281
                            ")->first();
3282
            $childbody = $r['body'];
3283
            $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
3284
3285
            $data[] =  [
3286
                'body'			=> $body,
3287
                'hdate'			=> $hdate,
3288
                'list_url'		=> $list_url,
3289
                'totalcomments'	=> $totalcomments,
3290
                'child'			=>  [
3291
                    'body'		=> $childbody,
3292
                    'speaker'	=> $speaker,
3293
                ],
3294
                'parent'		=>  [
3295
                    'body'		=> $parentbody,
3296
                ],
3297
            ];
3298
3299
        }
3300
3301
        $data =  [
3302
            'info' => [],
3303
            'data' => $data,
3304
        ];
3305
3306
        return $data;
3307
3308
    }
3309
3310
}
3311
3312
class StandingCommittee extends DEBATELIST {
3313
    public $major = 6;
3314
    public $listpage = 'pbc_clause';
3315
    public $commentspage = 'pbc_speech';
3316
    public $gidprefix = 'uk.org.publicwhip/standing/';
3317
3318
    public function __construct($session = '', $title = '') {
3319
        parent::__construct();
3320
        $this->bill_title = $title;
3321
        $title = str_replace(' ', '_', $title);
3322
        $this->url = urlencode($session) . '/' . urlencode($title) . '/';
3323
    }
3324
3325
    public function _get_committee($bill_id) {
3326
        include_once INCLUDESPATH . "easyparliament/member.php";
3327
        $q = $this->db->query(
3328
            'select count(*) as c from hansard
3329
                where major=6 and minor=:bill_id and htype=10',
3330
            [':bill_id' => $bill_id]
3331
        )->first();
3332
        $sittings = $q['c'];
3333
        $q = $this->db->query(
3334
            'select person_id,sum(attending) as attending, sum(chairman) as chairman
3335
                from pbc_members
3336
                where bill_id = :bill_id group by person_id',
3337
            [':bill_id' => $bill_id]
3338
        );
3339
        $comm = ['sittings' => $sittings, 'chairmen' => [], 'members' => []];
3340
        foreach ($q as $row) {
3341
            $person_id = $row['person_id'];
3342
            $mp = new MEMBER(['person_id' => $person_id]);
3343
            $attending = $row['attending'];
3344
            $chairman = $row['chairman'];
3345
            $arr = [
3346
                'name' => $mp->full_name(),
3347
                'attending' => $attending,
3348
            ];
3349
            if ($chairman) {
3350
                $comm['chairmen'][$person_id] = $arr;
3351
            } else {
3352
                $comm['members'][$person_id] = $arr;
3353
            }
3354
        }
3355
        return $comm;
3356
    }
3357
3358
    public function _get_data_by_bill($args) {
3359
        global $DATA, $this_page;
3360
        $data = [];
3361
        $input =  [
3362
            'amount' =>  [
3363
                'body' => true,
3364
                'comment' => true,
3365
                'excerpt' => true,
3366
            ],
3367
            'where' =>  [
3368
                'htype=' => '10',
3369
                'major=' => $this->major,
3370
                'minor=' => $args['id'],
3371
            ],
3372
            'order' => 'hdate,hpos',
3373
        ];
3374
        $sections = $this->_get_hansard_data($input);
3375
        $data['rows'] = [];
3376
        if (count($sections) > 0) {
3377
            $num_sections = count($sections);
3378
            for ($n = 0; $n < $num_sections; $n++) {
3379
                $sectionrow = $sections[$n];
3380
                [$sitting, $part] = $this->_get_sitting($sectionrow['gid']);
3381
                $sectionrow['sitting'] = $sitting;
3382
                $sectionrow['part'] = $part;
3383
                $input =  [
3384
                    'amount' =>  [
3385
                        'body' => true,
3386
                        'comment' => true,
3387
                        'excerpt' => true,
3388
                    ],
3389
                    'where' =>  [
3390
                        'section_id='	=> $sectionrow['epobject_id'],
3391
                        'htype='	=> '11',
3392
                        'major='	=> $this->major,
3393
                    ],
3394
                    'order' => 'hpos',
3395
                ];
3396
                $rows = $this->_get_hansard_data($input);
3397
                array_unshift($rows, $sectionrow);
3398
                $data['rows'] = array_merge($data['rows'], $rows);
3399
            }
3400
        }
3401
        $data['info']['bill'] = $args['title'];
3402
        $data['info']['major'] = $this->major;
3403
        $data['info']['committee'] = $this->_get_committee($args['id']);
3404
        $DATA->set_page_metadata($this_page, 'title', $args['title']);
3405
        return $data;
3406
    }
3407
3408
    public function _get_data_by_session($args) {
3409
        global $DATA, $this_page;
3410
        $session = $args['session'];
3411
        $q = $this->db->query(
3412
            'select id, title from bills where session = :session order by title',
3413
            [':session' => $session]
3414
        );
3415
        $bills = [];
3416
        foreach ($q as $row) {
3417
            $bills[$row['id']] = $row['title'];
3418
        }
3419
        if (!count($bills)) {
3420
            return [];
3421
        }
3422
        $q = $this->db->query('select minor,count(*) as c from hansard where major=6 and htype=12
3423
            and minor in (' . join(',', array_keys($bills)) . ')
3424
            group by minor');
3425
        $counts = [];
3426
        # $comments = array();
3427
        foreach ($q as $row) {
3428
            $minor = $row['minor'];
3429
            $counts[$minor] = $row['c'];
3430
            # $comments[$minor] = 0;
3431
        }
3432
        /*
3433
        $q = $this->db->query('select minor,epobject_id from hansard where major=6 and htype=10
3434
            and minor in (' . join(',', array_keys($bills)) . ')');
3435
        foreach ($q as $row) {
3436
            $comments[$row['minor']] += $this->_get_comment_count_for_epobject(array(
3437
                'epobject_id' => $row['epobject_id'],
3438
                'htype' => 10,
3439
            ));
3440
        }
3441
        */
3442
        $data = [];
3443
        foreach ($bills as $id => $title) {
3444
            $data[] = [
3445
                'title' => $title,
3446
                'url' => "/pbc/" . urlencode($session) . '/' . urlencode(str_replace(' ', '_', $title)) . '/',
3447
                'contentcount' => $counts[$id] ?? '???',
3448
                # 'totalcomments' => isset($comments[$id]) ? $comments[$id] : '???',
3449
            ];
3450
        }
3451
3452
        $YEARURL = new \MySociety\TheyWorkForYou\Url('pbc_session');
3453
        $nextprev = [];
3454
        $nextprev['prev'] =  ['body' => 'Previous session', 'title' => ''];
3455
        $nextprev['next'] =  ['body' => 'Next session', 'title' => ''];
3456
        $q = $this->db->query(
3457
            "SELECT session FROM bills WHERE session < :session ORDER BY session DESC LIMIT 1",
3458
            [':session' => $session]
3459
        )->first();
3460
        if ($q) {
3461
            $nextprev['prev']['url'] = $YEARURL->generate() . $q['session'] . '/';
3462
        }
3463
        $q = $this->db->query(
3464
            "SELECT session FROM bills WHERE session > :session ORDER BY session ASC LIMIT 1",
3465
            [':session' => $session]
3466
        )->first();
3467
        if ($q) {
3468
            $nextprev['next']['url'] = $YEARURL->generate() . $q['session'] . '/';
3469
        }
3470
        $DATA->set_page_metadata($this_page, 'nextprev', $nextprev);
3471
3472
        return $data;
3473
    }
3474
3475
    public function _get_data_by_recent_pbc_debates($args) {
3476
        if (!isset($args['num'])) {
3477
            $args['num'] = 20;
3478
        }
3479
        $q = $this->db->query('select gid, minor, hdate from hansard
3480
            where htype=10 and major=6
3481
            order by hdate desc limit ' . $args['num']);
3482
        $data = [];
3483
        foreach ($q as $row) {
3484
            $minor = $row['minor'];
3485
            $gid = $row['gid'];
3486
            $hdate = format_date($row['hdate'], LONGDATEFORMAT);
3487
            $qq = $this->db->query('select title, session from bills where id=' . $minor)->first();
3488
            $title = $qq['title'];
3489
            $session = $qq['session'];
3490
            [$sitting, $part] = $this->_get_sitting($gid);
3491
            $sitting_txt = make_ranking($sitting) . ' sitting';
3492
            if ($part > 0) {
3493
                $sitting .= ", part $part";
3494
            }
3495
            $data[$hdate][] = [
3496
                'bill' => $title,
3497
                'sitting' => $sitting_txt,
3498
                'url' => "/pbc/$session/" . urlencode(str_replace(' ', '_', $title)) . '/#sitting' . $sitting,
3499
            ];
3500
        }
3501
        return $data;
3502
    }
3503
3504
    # Given a GID, parse out the sitting number and optional part from it
3505
    public function _get_sitting($gid) {
3506
        if (preg_match('#_(\d\d)-(\d)_#', $gid, $m)) {
3507
            return [$m[1] + 0, $m[2]];
3508
        }
3509
        return [0, 0];
3510
    }
3511
}
3512