HANSARDLIST::_get_hansard_data()   F
last analyzed

Complexity

Conditions 57
Paths > 20000

Size

Total Lines 299
Code Lines 131

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 89
CRAP Score 117.121

Importance

Changes 0
Metric Value
cc 57
eloc 131
c 0
b 0
f 0
nc 52254816
nop 1
dl 0
loc 299
ccs 89
cts 121
cp 0.7355
crap 117.121
rs 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
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
                            AND (
2270
                                c.posted < '2025-01-01' OR
2271
                                u.can_annotate = 1
2272
                            )
2273
                            ORDER BY c.posted ASC
2274
                            LIMIT	1",
2275
                    [':epobject_id' => $item_data['epobject_id']]
2276
                )->first();
2277
2278
                // Add this comment to the data structure.
2279
                $comment =  [
2280
                    'comment_id' => $q['comment_id'],
2281
                    'user_id'	=> $q['user_id'],
2282
                    'body'		=> $q['body'],
2283
                    'posted'	=> $q['posted'],
2284
                    'username'	=> $q['firstname'] . ' ' . $q['lastname'],
2285 3
                ];
2286 3
            }
2287
2288
        }
2289 3
2290
        // We don't currently allow people to post comments to a section
2291
        // or subsection itself, only the items within them. So
2292
        // we don't get the most recent comment. Because there isn't one.
2293 3
2294 3
        $return =  [
2295
            'totalcomments' => $totalcomments,
2296
            'comment' => $comment,
2297
        ];
2298 3
2299 3
        return $return;
2300
    }
2301
2302
2303 3
    public function _get_comment_count_for_epobject($item_data) {
2304 3
        global $hansardmajors;
2305
        // What it says on the tin.
2306
        // $item_data must have 'htype' and 'epobject_id' elements. TODO: Check for major==4
2307 3
2308
        if (($hansardmajors[$this->major]['type'] == 'debate') &&
2309
            ($item_data['htype'] == '10' || $item_data['htype'] == '11')
2310 3
        ) {
2311
            // We'll be getting a count of the comments on all items
2312
            // within this (sub)section.
2313
            $from = "comments, hansard, users";
2314
            $where = "comments.epobject_id = hansard.epobject_id
2315
                    AND subsection_id = :epobject_id
2316
                    AND comments.user_id = users.user_id
2317
                    AND (
2318
                        comments.posted < '2025-01-01' OR
2319 3
                        users.can_annotate = 1
2320 3
                    )";
2321 3
2322
            if ($item_data['htype'] == '10') {
2323 3
                // Section - get a count of comments within this section that
2324
                // don't have a subsection heading.
2325 3
                $where .= " AND section_id = :epobject_id";
2326
            }
2327
2328 1
        } else {
2329
            // Just getting a count of the comments on this item.
2330
            $from = "comments, users";
2331
            $where = "epobject_id = :epobject_id
2332 1
                    AND comments.user_id = users.user_id
2333
                    AND (
2334 1
                        comments.posted < '2025-01-01' OR
2335
                        users.can_annotate = 1
2336
                    )";
2337 1
        }
2338 1
2339
        $q = $this->db->query(
2340
            "SELECT COUNT(*) AS count
2341
                        FROM 	$from
2342
                        WHERE	$where
2343 1
                        AND		visible = 1",
2344
            [':epobject_id' => $item_data['epobject_id']]
2345
        )->first();
2346
2347
        return $q['count'];
2348
    }
2349
2350
    public function _get_data_by_gid($args) {
2351
2352
        // We need to get the data for this gid.
2353
        // Then depending on what htype it is, we get the data for other items too.
2354
        global $DATA, $this_page, $hansardmajors;
2355
2356
        twfy_debug(get_class($this), "getting data by gid");
2357
2358
        // Get the information about the item this URL refers to.
2359
        $itemdata = $this->_get_item($args);
2360 1
        if (!$itemdata) {
2361
            return [];
2362
        }
2363
2364
        // If part of a Written Answer (just question or just answer), select the whole thing
2365
        if (isset($itemdata['major']) && $hansardmajors[$itemdata['major']]['type'] == 'other' and ($itemdata['htype'] == '12' or $itemdata['htype'] == '13' or $itemdata['htype'] == '14')) {
2366
            // find the gid of the subheading which holds this part
2367
            $input =  [
2368
                'amount' => ['gid' => true],
2369
                'where' =>  [
2370
                    'epobject_id=' => $itemdata['subsection_id'],
2371
                ],
2372
            ];
2373
            $parent = $this->_get_hansard_data($input);
2374
            // display that item, i.e. the whole of the Written Answer
2375
            twfy_debug(get_class($this), "instead of " . $args['gid'] . " selecting subheading gid " . $parent[0]['gid'] . " to get whole wrans");
2376
            $args['gid'] = $parent[0]['gid'];
2377
            $this->_get_item($args);
2378
            throw new RedirectException($args['gid']);
2379 1
        }
2380
2381 1
        # If a WMS main heading, go to next gid
2382 1
        if (isset($itemdata['major']) && $itemdata['major'] == 4 && $itemdata['htype'] == '10') {
2383 1
            $input =  [
2384
                'amount' => ['gid' => true],
2385
                'where' => [
2386 1
                    'section_id=' => $itemdata['epobject_id'],
2387
                ],
2388
                'order' => 'hpos ASC',
2389 1
                'limit' => 1,
2390 1
            ];
2391
            $next = $this->_get_hansard_data($input);
2392 1
            if (!empty($next)) {
2393 1
                twfy_debug(get_class($this), 'instead of ' . $args['gid'] . ' moving to ' . $next[0]['gid']);
2394
                $args['gid'] = $next[0]['gid'];
2395
                $this->_get_item($args);
2396
                throw new RedirectException($args['gid']);
2397 1
            }
2398 1
        }
2399 1
2400
        // Where we'll put all the data we want to render.
2401
        $data = [];
2402
2403 1
        if (isset($itemdata['htype'])) {
2404
            $this->htype = $itemdata['htype'];
2405
            if ($this->htype >= 12) {
2406 1
                $this_page = $this->commentspage;
2407
            } else {
2408
                $this_page = $this->listpage;
2409
            }
2410 1
        }
2411
        if (isset($itemdata['epobject_id'])) {
2412
            $this->epobject_id = $itemdata['epobject_id'];
2413
        }
2414
        if (isset($itemdata['gid'])) {
2415
            $this->gid = $itemdata['gid'];
2416
        }
2417 1
2418 1
        // We'll use these for page headings/titles:
2419
        $data['info']['date'] = $itemdata['hdate'];
2420
        $data['info']['text'] = $itemdata['body'];
2421
        $data['info']['major'] = $this->major;
2422 1
2423 1
        // If we have a member id we'll pass it on to the template so it
2424 1
        // can highlight all their speeches.
2425
        if (isset($args['member_id'])) {
2426
            $data['info']['member_id'] = $args['member_id'];
2427
        }
2428
        if (isset($args['person_id'])) {
2429
            $data['info']['person_id'] = $args['person_id'];
2430
        }
2431
2432
        if (isset($args['s']) && $args['s'] != '') {
2433
            // We have some search term words that we could highlight
2434
            // when rendering.
2435
            $data['info']['searchstring'] = $args['s'];
2436
        }
2437
2438
        // Get the section and subsection headings for this item.
2439
        $sectionrow = $this->_get_section($itemdata);
2440
        $subsectionrow = $this->_get_subsection($itemdata);
2441
2442
        // Get the nextprev links for this item, to link to next/prev pages.
2443
        // Duh.
2444
        if ($itemdata['htype'] == '10') {
2445 1
            $nextprev = $this->_get_nextprev_items($sectionrow);
2446
            $data['info']['text_heading'] = $itemdata['body'];
2447
2448
        } elseif ($itemdata['htype'] == '11') {
2449
            $nextprev = $this->_get_nextprev_items($subsectionrow);
2450
            $data['info']['text_heading'] = $itemdata['body'];
2451 1
2452
        } else {
2453
            // Ordinary lowly item.
2454
            $nextprev = $this->_get_nextprev_items($itemdata);
2455
2456
            if (isset($subsectionrow['gid'])) {
2457 1
                $nextprev['up']['url'] 		= $subsectionrow['listurl'];
2458
                $nextprev['up']['title'] 	= $subsectionrow['body'];
2459
            } else {
2460
                $nextprev['up']['url'] 		= $sectionrow['listurl'];
2461
                $nextprev['up']['title'] 	= $sectionrow['body'];
2462 1
            }
2463
            $nextprev['up']['body']		= gettext('See the whole debate');
2464 1
        }
2465 1
2466
        // We can then access this from $PAGE and the templates.
2467 1
        $DATA->set_page_metadata($this_page, 'nextprev', $nextprev);
2468
2469
        // Now get all the non-heading rows.
2470 1
2471 1
        // What data do we want for each item?
2472
        $amount =  [
2473
            'body' => true,
2474
            'speaker' => true,
2475 1
            'comment' => true,
2476
            'votes' => true,
2477
        ];
2478
2479
        if ($itemdata['htype'] == '10') {
2480 1
            // This item is a section, so we're displaying all the items within
2481 1
            // it that aren't within a subsection.
2482 1
2483
            $input =  [
2484 1
                'amount' => $amount,
2485
                'where' =>  [
2486 1
                    'section_id=' => $itemdata['epobject_id'],
2487
                    'subsection_id=' => $itemdata['epobject_id'],
2488 1
                ],
2489 1
                'order' => 'hpos ASC',
2490
            ];
2491
2492
            $data['rows'] = $this->_get_hansard_data($input);
2493
            if (!count($data['rows']) || (count($data['rows']) == 1 && strstr($data['rows'][0]['body'], 'was asked'))) {
2494
2495
                $input =  [
2496
                    'amount' =>  [
2497
                        'body' => true,
2498
                        'comment' => true,
2499
                        'excerpt' => true,
2500
                    ],
2501
                    'where' =>  [
2502
                        'section_id='	=> $sectionrow['epobject_id'],
2503
                        'htype='		=> '11',
2504
                        'major='		=> $this->major,
2505
                    ],
2506
                    'order' => 'hpos',
2507
                ];
2508
                $data['subrows'] = $this->_get_hansard_data($input);
2509
                # If there's only one subheading, and nothing in the heading, redirect to it immediaetly
2510
                if (count($data['subrows']) == 1) {
2511
                    throw new RedirectException($data['subrows'][0]['gid']);
2512
                }
2513
            }
2514 1
2515 1
        } elseif ($itemdata['htype'] == '11') {
2516
            // This item is a subsection, so we're displaying everything within it.
2517
2518
            $input =  [
2519
                'amount' => $amount,
2520
                'where' =>  [
2521 1
                    'subsection_id=' => $itemdata['epobject_id'],
2522
                ],
2523 1
                'order' => 'hpos ASC',
2524
            ];
2525
2526
            $data['rows'] = $this->_get_hansard_data($input);
2527
2528
2529
        } elseif ($itemdata['htype'] == '12' || $itemdata['htype'] == '13' || $itemdata['htype'] == '14') {
2530
            // Debate speech or procedural, so we're just displaying this one item.
2531
2532
            $data['rows'][] = $itemdata;
2533
2534
        }
2535
2536
        // Put the section and subsection at the top of the rows array.
2537
        if (count($subsectionrow) > 0 &&
2538
            $subsectionrow['gid'] != $sectionrow['gid']) {
2539
            // If we're looking at a section, there may not be a subsection.
2540
            // And if the subsectionrow and sectionrow aren't the same.
2541
            array_unshift($data['rows'], $subsectionrow);
2542
        }
2543
2544
        array_unshift($data['rows'], $sectionrow);
2545
2546
        return $data;
2547
2548
    }
2549
2550
    public function _get_data_by_column($args) {
2551
        global $this_page;
2552
2553
        twfy_debug(get_class($this), "getting data by column");
2554
2555
        $input = [ 'amount' => ['body' => true, 'comment' => true, 'speaker' => true],
2556
            'where' => [ 'hdate=' => $args['date'], 'major=' => $this->major, 'gid LIKE ' => '%.' . $args['column'] . '.%' ],
2557
            'order' => 'hpos',
2558
        ];
2559
        $data = $this->_get_hansard_data($input);
2560
        #		$data = array();
2561
2562
        #		$itemdata = $this->_get_item($args);
2563
2564
        #		if ($itemdata) {
2565
        #	$data['info']['date'] = $itemdata['hdate'];
2566
        #			$data['info']['text'] = $itemdata['body'];
2567
        #			$data['info']['major'] = $this->major;
2568
        #		}
2569
        return $data;
2570
    }
2571
2572
}
2573
2574
class WMSLIST extends WRANSLIST {
2575
    public $major = 4;
2576
    public $listpage = 'wms';
2577
    public $commentspage = 'wms';
2578
    public $gidprefix = 'uk.org.publicwhip/wms/';
2579
2580
    public function _get_data_by_recent_wms($args = []) {
2581
        return $this->_get_data_by_recent_wrans($args);
2582
    }
2583
}
2584
2585
class WHALLLIST extends DEBATELIST {
2586
    public $major = 2;
2587
    public $listpage = 'whalls';
2588
    public $commentspage = 'whall';
2589
    public $gidprefix = 'uk.org.publicwhip/westminhall/';
2590
}
2591
2592
class NILIST extends DEBATELIST {
2593
    public $major = 5;
2594
    public $listpage = 'nidebates';
2595
    public $commentspage = 'nidebate';
2596
    public $gidprefix = 'uk.org.publicwhip/ni/';
2597
}
2598
2599
class SENEDDENLIST extends DEBATELIST {
2600
    public $major = 10;
2601
    public $listpage = 'senedddebates';
2602
    public $commentspage = 'senedddebate';
2603
    public $gidprefix = 'uk.org.publicwhip/senedd/en/';
2604
}
2605
2606
class SENEDDCYLIST extends DEBATELIST {
2607
    public $major = 11;
2608
    public $listpage = 'senedddebates';
2609
    public $commentspage = 'senedddebate';
2610
    public $gidprefix = 'uk.org.publicwhip/senedd/cy/';
2611
}
2612
2613
class LMQLIST extends WRANSLIST {
2614
    public $major = 9;
2615
    public $listpage = 'lmqs';
2616
    public $commentspage = 'lmqs';
2617
    public $gidprefix = 'uk.org.publicwhip/london-mayors-questions/';
2618
}
2619
2620
class SPLIST extends DEBATELIST {
2621
    public $major = 7;
2622
    public $listpage = 'spdebates';
2623
    public $commentspage = 'spdebate';
2624
    public $gidprefix = 'uk.org.publicwhip/spor/';
2625
}
2626
2627
class SPWRANSLIST extends WRANSLIST {
2628
    public $major = 8;
2629
    public $listpage = 'spwrans';
2630
    public $commentspage = 'spwrans';
2631
    public $gidprefix = 'uk.org.publicwhip/spwa/';
2632
2633
    public function get_gid_from_spid($spid) {
2634
        // Fix the common errors of S.0 instead of S.O and leading
2635
        // zeros in the numbers:
2636
        $fixed_spid = preg_replace('/(S[0-9]+)0-([0-9]+)/', '${1}O-${2}', $spid);
2637
        $fixed_spid = preg_replace('/(S[0-9]+\w+)-0*([0-9]+)/', '${1}-${2}', $fixed_spid);
2638
        $q = $this->db->query(
2639
            "select mentioned_gid from mentions where gid = :gid_from_spid and (type = 4 or type = 6)",
2640
            [':gid_from_spid' => 'uk.org.publicwhip/spq/' . $fixed_spid]
2641
        )->first();
2642
        $gid = $q['mentioned_gid'];
2643
        if ($gid) {
2644
            return $gid;
2645
        }
2646
        return null;
2647
    }
2648
    public function old_get_gid_from_spid($spid) {
2649
        $q = $this->db->query(
2650
            "select gid from hansard where gid like :gid_like",
2651
            [':gid_like' => 'uk.org.publicwhip/spwa/%.' . $spid . '.h']
2652
        )->first();
2653
        $gid = $q['gid'];
2654
        if ($gid) {
2655
            return str_replace('uk.org.publicwhip/spwa/', '', $gid);
2656
        }
2657
        return null;
2658
    }
2659
}
2660
2661
class LORDSDEBATELIST extends DEBATELIST {
2662
    public $major = 101;
2663
    public $listpage = 'lordsdebates';
2664
    public $commentspage = 'lordsdebate';
2665
    public $gidprefix = 'uk.org.publicwhip/lords/';
2666
}
2667
2668
class DEBATELIST extends HANSARDLIST {
2669
    public $major = 1;
2670
    public $listpage = 'debates';
2671
    public $commentspage = 'debate';
2672
    public $gidprefix = 'uk.org.publicwhip/debate/';
2673
2674
    public function _get_data_by_recent_mostvotes($args) {
2675
        // Get the most highly voted recent speeches.
2676
        // $args may have 'days'=>7 and/or 'num'=>5
2677
        // or something like that.
2678
2679
        // The most voted on things during how many recent days?
2680
        if (isset($args['days']) && is_numeric($args['days'])) {
2681
            $days = $args['days'];
2682
        } else {
2683
            $days = 7;
2684
        }
2685
2686
        // How many results?
2687
        if (isset($args['num']) && is_numeric($args['num'])) {
2688
            $items_to_list = $args['num'];
2689
        } else {
2690
            $items_to_list = 5;
2691
        }
2692
2693
        $q = $this->db->query("SELECT min(subsection_id) AS subsection_id,
2694
                                min(section_id) AS section_id,
2695
                                min(htype) AS htype,
2696
                                min(gid) AS gid,
2697
                                min(major) AS major, min(minor) AS minor,
2698
                                min(hdate) AS hdate, min(htime) AS htime,
2699
                                min(person_id) AS person_id,
2700
                                min(epobject.body) AS body,
2701
                                SUM(uservotes.vote) + anonvotes.yes_votes AS total_vote
2702
                        FROM	hansard,
2703
                                epobject
2704
                                LEFT OUTER JOIN uservotes ON epobject.epobject_id = uservotes.epobject_id
2705
                                LEFT OUTER JOIN anonvotes ON epobject.epobject_id = anonvotes.epobject_id
2706
                        WHERE		major = :major
2707
                        AND		hansard.epobject_id = epobject.epobject_id
2708
                        AND		hdate >= DATE_SUB(CURDATE(), INTERVAL $days DAY)
2709
                        GROUP BY epobject.epobject_id
2710
                        HAVING 	total_vote > 0
2711
                        ORDER BY total_vote DESC
2712
                        LIMIT	$items_to_list
2713
                        ", [':major' => $this->major]);
2714
2715
        // What we return.
2716
        $data =  [];
2717
        $speeches = [];
2718
        foreach ($q as $row) {
2719
            $speech =  [
2720
                'subsection_id' => $row['subsection_id'],
2721
                'section_id' => $row['section_id'],
2722
                'htype' => $row['htype'],
2723
                'major' => $row['major'],
2724
                'minor' => $row['minor'],
2725
                'hdate' => $row['hdate'],
2726
                'body' => $row['body'],
2727
                'votes' => $row['total_vote'],
2728
            ];
2729
2730
            // Remove the "uk.org.publicwhip/blah/" from the gid:
2731
            // (In includes/utility.php)
2732
            $speech['gid'] = fix_gid_from_db($row['gid']);
2733
            $speech['listurl'] = $this->_get_listurl($speech);
2734
            $speech['speaker'] = $this->_get_speaker($row['person_id'], $row['hdate'], $row['htime'], $this->major);
2735
            $speeches[] = $speech;
2736
        }
2737
2738
        if (count($speeches) > 0) {
2739
            // Get the subsection texts.
2740
2741
            $num_speeches = count($speeches);
2742
            for ($n = 0; $n < $num_speeches; $n++) {
2743
                //if ($this->major == 1) {
2744
                // Debate.
2745
                $parent = $this->_get_subsection($speeches[$n]);
2746
2747
                //} elseif ($this->major == 3) {
2748
                // Wrans.
2749
                //	$parent = $this->_get_section ($speeches[$n]);
2750
                //}
2751
                // Add the parent's body on...
2752
                //if (isset($parent['body'])) {
2753
                $speeches[$n]['parent']['body'] = $parent['body'];
2754
                //} else {
2755
                //	$parent = $this->_get_section ($speeches[$n]);
2756
                //	$speeches[$n]['parent']['body'] = $parent['body'];
2757
                //}
2758
2759
            }
2760
2761
            $data['rows'] = $speeches;
2762
2763
        } else {
2764
            $data['rows'] =  [];
2765
        }
2766
2767
        $data['info']['days'] = $days;
2768
2769
        return $data;
2770
    }
2771
2772
2773
    public function total_speeches() {
2774
2775
        $q = $this->db->query("SELECT COUNT(*) AS count FROM hansard WHERE major = :major AND htype = 12", [':major' => $this->major]);
2776
2777
        return $q->first()['count'];
2778
    }
2779
2780
2781
    public function biggest_debates($args = []) {
2782
        // So we can just get the data back for special formatting
2783
        // on the front page, without doing the whole display() thing.
2784
        return $this->_get_data_by_biggest_debates($args);
2785
    }
2786
2787
    public function _get_data_by_featured_gid($args = []) {
2788
        $params = [];
2789
        $data = [];
2790
2791
        $params[':gid'] = $args['gid'];
2792
        $params[':major'] = $this->major;
2793
2794
        $query = "SELECT
2795
                    body,
2796
                    title,
2797
                    h.hdate,
2798
                    h.htime,
2799
                    h.htype,
2800
                    h.minor,
2801
                    h.gid,
2802
                    h.person_id,
2803
                    h.subsection_id,
2804
                    h.section_id,
2805
                    h.epobject_id
2806
            FROM    hansard h, epobject e
2807
            WHERE   h.major = :major
2808
            AND     h.gid = :gid
2809
            AND     h.epobject_id = e.epobject_id";
2810
2811
        $q = $this->db->query($query, $params)->first();
2812
2813
        if ($q) {
2814
2815
            // This array just used for getting further data about this debate.
2816
            $item_data =  [
2817
                'major'         => $this->major,
2818
                'minor'         => $q['minor'],
2819
                'gid'           => fix_gid_from_db($q['gid']),
2820
                'htype'         => $q['htype'],
2821
                'section_id'    => $q['section_id'],
2822
                'subsection_id' => $q['subsection_id'],
2823
                'epobject_id'   => $q['epobject_id'],
2824
            ];
2825
2826
            $list_url      = $this->_get_listurl($item_data);
2827
            $totalcomments = $this->_get_comment_count_for_epobject($item_data);
2828
2829
            $body          = $q['body'];
2830
            $hdate         = $q['hdate'];
2831
            $htime         = $q['htime'];
2832
2833
            // If this is a subsection, we're going to prepend the title
2834
            // of the parent section, so let's get that.
2835
            $parentbody = '';
2836
            if ($item_data['htype'] == 12) {
2837
                $r = $this->db->query(
2838
                    "SELECT sec.body as sec_body, sub.body as sub_bod
2839
                                FROM    epobject sec, epobject sub
2840
                                WHERE   sec.epobject_id = :section_id
2841
                                AND     sub.epobject_id = :subsection_id",
2842
                    [
2843
                        ':section_id' => $item_data['section_id'],
2844
                        ':subsection_id' => $item_data['subsection_id'],
2845
                    ]
2846
                )->first();
2847
                $section_body = $r['sec_body'];
2848
                $subsection_body = $r['sub_body'];
2849
                if ($section_body != $subsection_body) {
2850
                    $parentbody = "$section_body : $subsection_body";
2851
                } else {
2852
                    $parentbody = $section_body;
2853
                }
2854
            } elseif ($item_data['htype'] == 11) {
2855
                $r = $this->db->query(
2856
                    "SELECT body FROM epobject WHERE epobject_id = :section_id",
2857
                    [
2858
                        ':section_id' => $item_data['section_id'],
2859
                    ]
2860
                )->first();
2861
                $parentbody = $r['body'];
2862
            } elseif ($item_data['htype'] == 10) {
2863
                $parentbody = $body;
2864
            }
2865
2866
            // Get the question for this item.
2867
            if ($item_data['htype'] == 12) {
2868
                $childbody = $body;
2869
                $speaker = $this->_get_speaker($q['person_id'], $q['hdate'], $q['htime'], $this->major);
2870
            } else {
2871
                $r = $this->db->query(
2872
                    "SELECT e.body, e.title,
2873
                                        h.person_id, h.hdate, h.htime
2874
                                FROM    hansard h, epobject e
2875
                                WHERE   h.epobject_id = e.epobject_id
2876
                                AND     h.subsection_id = :object_id
2877
                                ORDER BY hpos
2878
                                LIMIT 1
2879
                                ",
2880
                    [ ':object_id' => $item_data['epobject_id'] ]
2881
                )->first();
2882
                $childbody = $r['body'];
2883
                $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
2884
            }
2885
2886
            $contentcount = 0;
2887
            $r = $this->db->query(
2888
                "SELECT COUNT(*) AS count
2889
                            FROM hansard
2890
                            WHERE subsection_id = :object_id
2891
                            AND htype = 12",
2892
                [':object_id' => $item_data['epobject_id']]
2893
            )->first();
2894
2895
            if ($r) {
2896
                $contentcount = $r['count'];
2897
            }
2898
2899
            global $hansardmajors;
2900
            $more_url = new \MySociety\TheyWorkForYou\Url($hansardmajors[$this->major]['page_all']);
2901
            $details = [
2902
                'body'          => $body,
2903
                'contentcount'  => $contentcount,
2904
                'hdate'         => $hdate,
2905
                'htime'         => $htime,
2906
                'list_url'      => $list_url,
2907
                'totalcomments' => $totalcomments,
2908
                'child'         => [
2909
                    'body'      => $childbody,
2910
                    'speaker'   => $speaker,
2911
                ],
2912
                'parent'        => [
2913
                    'body'      => $parentbody,
2914
                ],
2915
                'desc' => $hansardmajors[$this->major]['title'],
2916
                'more_url' => $more_url->generate(),
2917
            ];
2918
2919
            $data =  [
2920
                'gid' => $args['gid'],
2921
                'major' => $this->major,
2922
                'info' => [],
2923
                'data' => $details,
2924
            ];
2925
        }
2926
2927
        return $data;
2928
2929
    }
2930
    public function _get_data_by_recent_debates($args = []) {
2931
        // Returns an array of some random recent debates from a set number of
2932
        // recent days (that's recent days starting from the most recent day
2933
        // that had any debates on).
2934
2935
        // $args['days'] is the number of days back to look for biggest debates (1 by default).
2936
        // $args['num'] is the number of links to return (1 by default).
2937
2938
        $data = [];
2939
2940
        $params = [];
2941
2942
        // Get the most recent day on which we have a debate.
2943
        $recentday = $this->most_recent_day();
2944
        if (!count($recentday)) {
2945
            return $data;
2946
        }
2947
2948
        if (!isset($args['days']) || !is_numeric($args['days'])) {
2949
            $args['days'] = 1;
2950
        }
2951
        if (!isset($args['num']) || !is_numeric($args['num'])) {
2952
            $args['num'] = 1;
2953
        }
2954
2955
        if ($args['num'] == 1) {
2956
            $datewhere = "h.hdate = :hdate";
2957
            $params[':hdate'] = $recentday['hdate'];
2958
        } else {
2959
            $firstdate = gmdate('Y-m-d', $recentday['timestamp'] - (86400 * $args['days']));
2960
            $datewhere = "h.hdate >= :firstdate
2961
                        AND h.hdate <= :hdate";
2962
            $params[':firstdate'] = $firstdate;
2963
            $params[':hdate'] = $recentday['hdate'];
2964
        }
2965
2966
        $params[':limit'] = $args['num'];
2967
        $params[':major'] = $this->major;
2968
2969
        $query = "SELECT COUNT(*) AS count,
2970
                    min(body) AS body,
2971
                    min(h.hdate) AS hdate,
2972
                    min(sech.htype) AS htype,
2973
                    min(sech.htime) AS htime,
2974
                    min(sech.gid) AS gid,
2975
                    min(sech.subsection_id) AS subsection_id,
2976
                    min(sech.section_id) AS section_id,
2977
                    min(sech.epobject_id) AS epobject_id
2978
            FROM    hansard h, epobject e, hansard sech
2979
            WHERE   h.major = :major
2980
            AND     $datewhere
2981
            AND     h.subsection_id = e.epobject_id
2982
            AND     sech.epobject_id = h.subsection_id
2983
            GROUP BY h.subsection_id
2984
            HAVING  count >= 5
2985
            ORDER BY RAND()
2986
            LIMIT   :limit";
2987
2988
        $q = $this->db->query($query, $params);
2989
        foreach ($q as $row) {
2990
2991
            // This array just used for getting further data about this debate.
2992
            $item_data =  [
2993
                'major' => $this->major,
2994
                'gid' => fix_gid_from_db($row['gid']),
2995
                'htype' => $row['htype'],
2996
                'section_id' => $row['section_id'],
2997
                'subsection_id' => $row['subsection_id'],
2998
                'epobject_id' => $row['epobject_id'],
2999
            ];
3000
3001
            $list_url      = $this->_get_listurl($item_data);
3002
            $totalcomments = $this->_get_comment_count_for_epobject($item_data);
3003
3004
            $contentcount  = $row['count'];
3005
            $body          = $row['body'];
3006
            $hdate         = $row['hdate'];
3007
            $htime         = $row['htime'];
3008
3009
            // If this is a subsection, we're going to prepend the title
3010
            // of the parent section, so let's get that.
3011
            $parentbody = '';
3012
            if ($item_data['htype'] == 11) {
3013
                $r = $this->db->query(
3014
                    "SELECT body
3015
                                FROM    epobject
3016
                                WHERE   epobject_id = :epobject_id",
3017
                    [':epobject_id' => $item_data['section_id']]
3018
                )->first();
3019
                $parentbody = $r['body'];
3020
            }
3021
3022
            // Get the question for this item.
3023
            $r = $this->db->query("SELECT e.body,
3024
                                    h.person_id, h.hdate, h.htime
3025
                            FROM    hansard h, epobject e
3026
                            WHERE   h.epobject_id = e.epobject_id
3027
                            AND     h.subsection_id = '" . $item_data['epobject_id'] . "'
3028
                            ORDER BY hpos
3029
                            LIMIT 1
3030
                            ")->first();
3031
            $childbody = $r['body'];
3032
            $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
3033
3034
            $data[] = [
3035
                'contentcount'  => $contentcount,
3036
                'body'          => $body,
3037
                'hdate'         => $hdate,
3038
                'htime'         => $htime,
3039
                'list_url'      => $list_url,
3040
                'totalcomments' => $totalcomments,
3041
                'child'         => [
3042
                    'body'      => $childbody,
3043
                    'speaker'   => $speaker,
3044
                ],
3045
                'parent'        => [
3046
                    'body'      => $parentbody,
3047
                ],
3048
            ];
3049
3050
        }
3051
3052
        $data =  [
3053
            'info' => [],
3054
            'data' => $data,
3055
        ];
3056
3057
        return $data;
3058
3059
    }
3060
3061
    public function _get_data_by_biggest_debates($args = []) {
3062
        // Returns an array of the debates with most speeches in from
3063
        // a set number of recent days (that's recent days starting from the
3064
        // most recent day that had any debates on).
3065
3066
        // $args['days'] is the number of days back to look for biggest debates.
3067
        // (1 by default)
3068
        // $args['num'] is the number of links to return (1 by default).
3069
3070
        $data = [];
3071
3072
        // Get the most recent day on which we have a debate.
3073
        $recentday = $this->most_recent_day();
3074
        if (!count($recentday)) {
3075
            return [];
3076
        }
3077
3078
        if (!isset($args['days']) || !is_numeric($args['days'])) {
3079
            $args['days'] = 1;
3080
        }
3081
        if (!isset($args['num']) || !is_numeric($args['num'])) {
3082
            $args['num'] = 1;
3083
        }
3084
3085
        $params = [':recentdate' => $recentday['hdate']];
3086
        if ($args['num'] == 1) {
3087
            $datewhere = "h.hdate = :recentdate";
3088
        } else {
3089
            $params[':firstdate'] = gmdate('Y-m-d', $recentday['timestamp'] - (86400 * $args['days']));
3090
            $datewhere = "h.hdate >= :firstdate AND	h.hdate <= :recentdate";
3091
        }
3092
3093
        $params[':limit'] = $args['num'];
3094
        $params[':major'] = $this->major;
3095
3096
        $q = $this->db->query("SELECT COUNT(*) AS count,
3097
                                min(body) AS body,
3098
                                min(h.hdate) AS hdate,
3099
                                min(sech.htype) AS htype,
3100
                                min(sech.gid) AS gid,
3101
                                min(sech.subsection_id) AS subsection_id,
3102
                                min(sech.section_id) AS section_id,
3103
                                min(sech.epobject_id) AS epobject_id
3104
                        FROM 	hansard h, epobject e, hansard sech
3105
                        WHERE 	h.major = :major
3106
                        AND 	$datewhere
3107
                        AND  	h.subsection_id = e.epobject_id
3108
                        AND 	sech.epobject_id = h.subsection_id
3109
                        GROUP BY h.subsection_id
3110
                        ORDER BY count DESC
3111
                        LIMIT :limit", $params);
3112
3113
        foreach ($q as $row) {
3114
3115
            // This array just used for getting further data about this debate.
3116
            $item_data =  [
3117
                'major' => $this->major,
3118
                'gid' => fix_gid_from_db($row['gid']),
3119
                'htype' => $row['htype'],
3120
                'section_id' => $row['section_id'],
3121
                'subsection_id' => $row['subsection_id'],
3122
                'epobject_id' => $row['epobject_id'],
3123
            ];
3124
3125
            $list_url 		= $this->_get_listurl($item_data);
3126
            $totalcomments	= $this->_get_comment_count_for_epobject($item_data);
3127
3128
            $contentcount = $row['count'];
3129
            $body = $row['body'];
3130
            $hdate = $row['hdate'];
3131
3132
3133
            // This array will be added to $data, which is what gets returned.
3134
            $debate =  [
3135
                'contentcount'	=> $contentcount,
3136
                'body'			=> $body,
3137
                'hdate'			=> $hdate,
3138
                'list_url'		=> $list_url,
3139
                'totalcomments'	=> $totalcomments,
3140
            ];
3141
3142
            // If this is a subsection, we're going to prepend the title
3143
            // of the parent section, so let's get that.
3144
            if ($item_data['htype'] == 11) {
3145
3146
                $r = $this->db->query(
3147
                    "SELECT body
3148
                                FROM	epobject
3149
                                WHERE	epobject_id = :epobject_id",
3150
                    [':epobject_id' => $item_data['section_id']]
3151
                )->first();
3152
                $debate['parent']['body'] = $r['body'];
3153
            }
3154
3155
            $r = $this->db->query("SELECT e.body,
3156
                                    h.person_id, h.hdate, h.htime
3157
                            FROM    hansard h, epobject e
3158
                            WHERE   h.epobject_id = e.epobject_id
3159
                            AND     h.subsection_id = '" . $item_data['epobject_id'] . "'
3160
                            ORDER BY hpos
3161
                            LIMIT 1
3162
                            ")->first();
3163
            $childbody = $r['body'];
3164
            $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
3165
3166
            $debate['child'] = [
3167
                'body' => $childbody,
3168
                'speaker' => $speaker,
3169
            ];
3170
3171
            $data[] = $debate;
3172
        }
3173
3174
        $data =  [
3175
            'info' => [],
3176
            'data' => $data,
3177
        ];
3178
3179
        return $data;
3180
3181
    }
3182
3183
}
3184
3185
3186
class WRANSLIST extends HANSARDLIST {
3187
    public $major = 3;
3188
    public $listpage = 'wrans';
3189
    public $commentspage = 'wrans'; // We don't have a separate page for wrans comments.
3190
    public $gidprefix = 'uk.org.publicwhip/wrans/';
3191
3192
    public function total_questions() {
3193
        $q = $this->db->query("SELECT COUNT(*) AS count FROM hansard WHERE major = :major AND minor = 1", [':major' => $this->major]);
3194
        return $q->first()['count'];
3195
    }
3196
3197
    public function _get_data_by_recent_wrans($args = []) {
3198
        global $hansardmajors;
3199
3200
        // $args['days'] is the number of days back to look for biggest debates.
3201
        // (1 by default)
3202
        // $args['num'] is the number of links to return (1 by default).
3203
3204
        $data = [];
3205
3206
        $params = [];
3207
3208
        // Get the most recent day on which we have wrans.
3209
        $recentday = $this->most_recent_day();
3210
        if (!count($recentday)) {
3211
            return $data;
3212
        }
3213
3214
        if (!isset($args['days']) || !is_numeric($args['days'])) {
3215
            $args['days'] = 1;
3216
        }
3217
        if (!isset($args['num']) || !is_numeric($args['num'])) {
3218
            $args['num'] = 1;
3219
        }
3220
3221
        if ($args['num'] == 1) {
3222
            $datewhere = "h.hdate = :datewhere";
3223
            $params[':datewhere'] = $recentday['hdate'];
3224
        } else {
3225
            $firstdate = gmdate('Y-m-d', $recentday['timestamp'] - (86400 * $args['days']));
3226
            $datewhere = "h.hdate >= :firstdate AND h.hdate <= :hdate";
3227
            $params[':firstdate'] = $firstdate;
3228
            $params[':hdate'] = $recentday['hdate'];
3229
        }
3230
3231
3232
        // Get a random selection of subsections in wrans.
3233
        if (in_array($hansardmajors[$this->major]['location'], ['Scotland', 'London'])) {
3234
            $htype = 'htype = 10 and section_id = 0';
3235
        } else {
3236
            $htype = 'htype = 11 and section_id != 0';
3237
        }
3238
3239
        $params[':limit'] = $args['num'];
3240
        $params[':major'] = $this->major;
3241
3242
        $query = "SELECT e.body,
3243
                    h.hdate,
3244
                    h.htype,
3245
                    h.gid,
3246
                    h.subsection_id,
3247
                    h.section_id,
3248
                    h.epobject_id
3249
            FROM    hansard h, epobject e
3250
            WHERE   h.major = :major
3251
            AND     $htype
3252
            AND     subsection_id = 0
3253
            AND     $datewhere
3254
            AND     h.epobject_id = e.epobject_id
3255
            ORDER BY RAND()
3256
            LIMIT   :limit";
3257
3258
        $q = $this->db->query($query, $params);
3259
3260
        foreach ($q as $row) {
3261
            // This array just used for getting further data about this debate.
3262
            $item_data =  [
3263
                'major' => $this->major,
3264
                'gid' => fix_gid_from_db($row['gid']),
3265
                'htype' => $row['htype'],
3266
                'section_id' => $row['section_id'],
3267
                'subsection_id' => $row['subsection_id'],
3268
                'epobject_id' => $row['epobject_id'],
3269
            ];
3270
3271
            $list_url 		= $this->_get_listurl($item_data);
3272
            $totalcomments	= $this->_get_comment_count_for_epobject($item_data);
3273
3274
            $body = $row['body'];
3275
            $hdate = $row['hdate'];
3276
3277
            // Get the parent section for this item.
3278
            $parentbody = '';
3279
            if ($row['section_id']) {
3280
                $r = $this->db->query("SELECT e.body
3281
                            FROM	hansard h, epobject e
3282
                            WHERE	h.epobject_id = e.epobject_id
3283
                            AND		h.epobject_id = '" . $row['section_id'] . "'
3284
                            ")->first();
3285
                $parentbody = $r['body'];
3286
            }
3287
3288
            // Get the question for this item.
3289
            $r = $this->db->query("SELECT e.body,
3290
                                    h.person_id, h.hdate, h.htime
3291
                            FROM	hansard h, epobject e
3292
                            WHERE	h.epobject_id = e.epobject_id
3293
                            AND 	h.subsection_id = '" . $row['epobject_id'] . "'
3294
                            ORDER BY hpos
3295
                            LIMIT 1
3296
                            ")->first();
3297
            $childbody = $r['body'];
3298
            $speaker = $this->_get_speaker($r['person_id'], $r['hdate'], $r['htime'], $this->major);
3299
3300
            $data[] =  [
3301
                'body'			=> $body,
3302
                'hdate'			=> $hdate,
3303
                'list_url'		=> $list_url,
3304
                'totalcomments'	=> $totalcomments,
3305
                'child'			=>  [
3306
                    'body'		=> $childbody,
3307
                    'speaker'	=> $speaker,
3308
                ],
3309
                'parent'		=>  [
3310
                    'body'		=> $parentbody,
3311
                ],
3312
            ];
3313
3314
        }
3315
3316
        $data =  [
3317
            'info' => [],
3318
            'data' => $data,
3319
        ];
3320
3321
        return $data;
3322
3323
    }
3324
3325
}
3326
3327
class StandingCommittee extends DEBATELIST {
3328
    public $major = 6;
3329
    public $listpage = 'pbc_clause';
3330
    public $commentspage = 'pbc_speech';
3331
    public $gidprefix = 'uk.org.publicwhip/standing/';
3332
3333
    public function __construct($session = '', $title = '') {
3334
        parent::__construct();
3335
        $this->bill_title = $title;
3336
        $title = str_replace(' ', '_', $title);
3337
        $this->url = urlencode($session) . '/' . urlencode($title) . '/';
3338
    }
3339
3340
    public function _get_committee($bill_id) {
3341
        include_once INCLUDESPATH . "easyparliament/member.php";
3342
        $q = $this->db->query(
3343
            'select count(*) as c from hansard
3344
                where major=6 and minor=:bill_id and htype=10',
3345
            [':bill_id' => $bill_id]
3346
        )->first();
3347
        $sittings = $q['c'];
3348
        $q = $this->db->query(
3349
            'select person_id,sum(attending) as attending, sum(chairman) as chairman
3350
                from pbc_members
3351
                where bill_id = :bill_id group by person_id',
3352
            [':bill_id' => $bill_id]
3353
        );
3354
        $comm = ['sittings' => $sittings, 'chairmen' => [], 'members' => []];
3355
        foreach ($q as $row) {
3356
            $person_id = $row['person_id'];
3357
            $mp = new MEMBER(['person_id' => $person_id]);
3358
            $attending = $row['attending'];
3359
            $chairman = $row['chairman'];
3360
            $arr = [
3361
                'name' => $mp->full_name(),
3362
                'attending' => $attending,
3363
            ];
3364
            if ($chairman) {
3365
                $comm['chairmen'][$person_id] = $arr;
3366
            } else {
3367
                $comm['members'][$person_id] = $arr;
3368
            }
3369
        }
3370
        return $comm;
3371
    }
3372
3373
    public function _get_data_by_bill($args) {
3374
        global $DATA, $this_page;
3375
        $data = [];
3376
        $input =  [
3377
            'amount' =>  [
3378
                'body' => true,
3379
                'comment' => true,
3380
                'excerpt' => true,
3381
            ],
3382
            'where' =>  [
3383
                'htype=' => '10',
3384
                'major=' => $this->major,
3385
                'minor=' => $args['id'],
3386
            ],
3387
            'order' => 'hdate,hpos',
3388
        ];
3389
        $sections = $this->_get_hansard_data($input);
3390
        $data['rows'] = [];
3391
        if (count($sections) > 0) {
3392
            $num_sections = count($sections);
3393
            for ($n = 0; $n < $num_sections; $n++) {
3394
                $sectionrow = $sections[$n];
3395
                [$sitting, $part] = $this->_get_sitting($sectionrow['gid']);
3396
                $sectionrow['sitting'] = $sitting;
3397
                $sectionrow['part'] = $part;
3398
                $input =  [
3399
                    'amount' =>  [
3400
                        'body' => true,
3401
                        'comment' => true,
3402
                        'excerpt' => true,
3403
                    ],
3404
                    'where' =>  [
3405
                        'section_id='	=> $sectionrow['epobject_id'],
3406
                        'htype='	=> '11',
3407
                        'major='	=> $this->major,
3408
                    ],
3409
                    'order' => 'hpos',
3410
                ];
3411
                $rows = $this->_get_hansard_data($input);
3412
                array_unshift($rows, $sectionrow);
3413
                $data['rows'] = array_merge($data['rows'], $rows);
3414
            }
3415
        }
3416
        $data['info']['bill'] = $args['title'];
3417
        $data['info']['major'] = $this->major;
3418
        $data['info']['committee'] = $this->_get_committee($args['id']);
3419
        $DATA->set_page_metadata($this_page, 'title', $args['title']);
3420
        return $data;
3421
    }
3422
3423
    public function _get_data_by_session($args) {
3424
        global $DATA, $this_page;
3425
        $session = $args['session'];
3426
        $q = $this->db->query(
3427
            'select id, title from bills where session = :session order by title',
3428
            [':session' => $session]
3429
        );
3430
        $bills = [];
3431
        foreach ($q as $row) {
3432
            $bills[$row['id']] = $row['title'];
3433
        }
3434
        if (!count($bills)) {
3435
            return [];
3436
        }
3437
        $q = $this->db->query('select minor,count(*) as c from hansard where major=6 and htype=12
3438
            and minor in (' . join(',', array_keys($bills)) . ')
3439
            group by minor');
3440
        $counts = [];
3441
        # $comments = array();
3442
        foreach ($q as $row) {
3443
            $minor = $row['minor'];
3444
            $counts[$minor] = $row['c'];
3445
            # $comments[$minor] = 0;
3446
        }
3447
        /*
3448
        $q = $this->db->query('select minor,epobject_id from hansard where major=6 and htype=10
3449
            and minor in (' . join(',', array_keys($bills)) . ')');
3450
        foreach ($q as $row) {
3451
            $comments[$row['minor']] += $this->_get_comment_count_for_epobject(array(
3452
                'epobject_id' => $row['epobject_id'],
3453
                'htype' => 10,
3454
            ));
3455
        }
3456
        */
3457
        $data = [];
3458
        foreach ($bills as $id => $title) {
3459
            $data[] = [
3460
                'title' => $title,
3461
                'url' => "/pbc/" . urlencode($session) . '/' . urlencode(str_replace(' ', '_', $title)) . '/',
3462
                'contentcount' => $counts[$id] ?? '???',
3463
                # 'totalcomments' => isset($comments[$id]) ? $comments[$id] : '???',
3464
            ];
3465
        }
3466
3467
        $YEARURL = new \MySociety\TheyWorkForYou\Url('pbc_session');
3468
        $nextprev = [];
3469
        $nextprev['prev'] =  ['body' => 'Previous session', 'title' => ''];
3470
        $nextprev['next'] =  ['body' => 'Next session', 'title' => ''];
3471
        $q = $this->db->query(
3472
            "SELECT session FROM bills WHERE session < :session ORDER BY session DESC LIMIT 1",
3473
            [':session' => $session]
3474
        )->first();
3475
        if ($q) {
3476
            $nextprev['prev']['url'] = $YEARURL->generate() . $q['session'] . '/';
3477
        }
3478
        $q = $this->db->query(
3479
            "SELECT session FROM bills WHERE session > :session ORDER BY session ASC LIMIT 1",
3480
            [':session' => $session]
3481
        )->first();
3482
        if ($q) {
3483
            $nextprev['next']['url'] = $YEARURL->generate() . $q['session'] . '/';
3484
        }
3485
        $DATA->set_page_metadata($this_page, 'nextprev', $nextprev);
3486
3487
        return $data;
3488
    }
3489
3490
    public function _get_data_by_recent_pbc_debates($args) {
3491
        if (!isset($args['num'])) {
3492
            $args['num'] = 20;
3493
        }
3494
        $q = $this->db->query('select gid, minor, hdate from hansard
3495
            where htype=10 and major=6
3496
            order by hdate desc limit ' . $args['num']);
3497
        $data = [];
3498
        foreach ($q as $row) {
3499
            $minor = $row['minor'];
3500
            $gid = $row['gid'];
3501
            $hdate = format_date($row['hdate'], LONGDATEFORMAT);
3502
            $qq = $this->db->query('select title, session from bills where id=' . $minor)->first();
3503
            $title = $qq['title'];
3504
            $session = $qq['session'];
3505
            [$sitting, $part] = $this->_get_sitting($gid);
3506
            $sitting_txt = make_ranking($sitting) . ' sitting';
3507
            if ($part > 0) {
3508
                $sitting .= ", part $part";
3509
            }
3510
            $data[$hdate][] = [
3511
                'bill' => $title,
3512
                'sitting' => $sitting_txt,
3513
                'url' => "/pbc/$session/" . urlencode(str_replace(' ', '_', $title)) . '/#sitting' . $sitting,
3514
            ];
3515
        }
3516
        return $data;
3517
    }
3518
3519
    # Given a GID, parse out the sitting number and optional part from it
3520
    public function _get_sitting($gid) {
3521
        if (preg_match('#_(\d\d)-(\d)_#', $gid, $m)) {
3522
            return [$m[1] + 0, $m[2]];
3523
        }
3524
        return [0, 0];
3525
    }
3526
}
3527