_sort()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 7
rs 10
1
<?php
2
/*
3
 * Name: alertmailer.php
4
 * Description: Mailer for email alerts
5
 * $Id: alertmailer.php,v 1.34 2009-06-23 10:11:10 matthew Exp $
6
 */
7
8
function mlog($message) {
9
    print $message;
10
}
11
12
include_once '../www/includes/easyparliament/init.php';
13
ini_set('memory_limit', -1);
14
include_once INCLUDESPATH . 'easyparliament/member.php';
15
16
$announcement_manager = new \MySociety\TheyWorkForYou\Model\AnnouncementManagement();
17
$banner = $announcement_manager->get_random_valid_item('alerts');
18
19
$text_banner = '';
20
$html_banner = '';
21
if ($banner) {
22
    $text_banner = $banner->title . "\n\n" . $banner->content . "\n\n";
23
    $html_banner = '<p><strong>' . $banner->title . "</strong></p><p>" . $banner->content . '</p>';
24
25
    if (property_exists($banner, 'url') && $banner->url) {
26
        $link_text = "Read more";
27
        if (property_exists($banner, 'button_text')) {
28
            $link_text = $banner->button_text;
29
        }
30
        $text_banner .= $banner->url . "\n\n";
31
        $html_banner .= '<p><a href="' . $banner->url . '">' . $link_text . '</a></p>';
32
    }
33
}
34
35
$global_start = getmicrotime();
36
$db = new ParlDB();
37
38
# Get current value of latest batch
39
$q = $db->query('SELECT max(indexbatch_id) as max_batch_id FROM indexbatch')->first();
40
$max_batch_id = $q['max_batch_id'];
41
mlog("max_batch_id: " . $max_batch_id . "\n");
42
43
# Last sent is timestamp of last alerts gone out.
44
# Last batch is the search index batch number last alert went out to.
45
if (is_file(RAWDATA . '/alerts-lastsent')) {
46
    $lastsent = file(RAWDATA . '/alerts-lastsent');
47
} else {
48
    $lastsent = ['', 0];
49
}
50
51
$lastupdated = trim($lastsent[0]);
52
if (!$lastupdated) {
53
    $lastupdated = strtotime('00:00 today');
54
}
55
$lastbatch = trim($lastsent[1]);
56
if (!$lastbatch) {
57
    $lastbatch = 0;
58
}
59
mlog("lastupdated: $lastupdated lastbatch: $lastbatch\n");
60
61
# Construct query fragment to select search index batches which
62
# have been made since last time we ran
63
$batch_query_fragment = "";
64
for ($i = $lastbatch + 1; $i <= $max_batch_id; $i++) {
65
    $batch_query_fragment .= "batch:$i ";
66
}
67
$batch_query_fragment = trim($batch_query_fragment);
68
mlog("batch_query_fragment: " . $batch_query_fragment . "\n");
69
70
if (!$batch_query_fragment) {
71
    mlog("No new batches since last run - nothing to run over!");
72
    exit;
73
}
74
75
# For testing purposes, specify nomail on command line to not send out emails
76
$nomail = false;
77
$onlyemail = '';
78
$fromemail = '';
79
$fromflag = false;
80
$toemail = '';
81
$template = 'alert_mailout';
82
for ($k = 1; $k < $argc; $k++) {
83
    if ($argv[$k] == '--nomail') {
84
        $nomail = true;
85
    }
86
    if (preg_match('#^--only=(.*)$#', $argv[$k], $m)) {
87
        $onlyemail = $m[1];
88
    }
89
    if (preg_match('#^--from=(.*)$#', $argv[$k], $m)) {
90
        $fromemail = $m[1];
91
    }
92
    if (preg_match('#^--to=(.*)$#', $argv[$k], $m)) {
93
        $toemail = $m[1];
94
    }
95
    if (preg_match('#^--template=(.*)$#', $argv[$k], $m)) {
96
        $template = $m[1];
97
        # Tee hee
98
        $template = "../../../../../../../../../../home/twfy-live/email-alert-templates/alert_mailout_$template";
99
    }
100
}
101
102
if (DEVSITE) {
103
    $nomail = true;
104
}
105
106
if ($nomail) {
107
    mlog("NOT SENDING EMAIL\n");
108
}
109
if (($fromemail && $onlyemail) || ($toemail && $onlyemail)) {
110
    mlog("Can't have both from/to and only!\n");
111
    exit;
112
}
113
114
$active = 0;
115
$queries = 0;
116
$unregistered = 0;
117
$registered = 0;
118
$sentemails = 0;
119
120
$LIVEALERTS = new ALERT();
121
122
$current = ['email' => '', 'token' => '', 'lang' => ''];
123
$email_text = '';
124
$html_text = '';
125
$globalsuccess = 1;
126
127
# Fetch all confirmed, non-deleted alerts
128
$confirmed = 1;
129
$deleted = 0;
130
$alertdata = $LIVEALERTS->fetch($confirmed, $deleted);
131
$alertdata = $alertdata['data'];
132
133
$DEBATELIST = new DEBATELIST(); # Nothing debate specific, but has to be one of them
134
135
$sects = [
136
    1 => gettext('Commons debate'),
137
    2 => gettext('Westminster Hall debate'),
138
    3 => gettext('Written Answer'),
139
    4 => gettext('Written Ministerial Statement'),
140
    5 => gettext('Northern Ireland Assembly debate'),
141
    6 => gettext('Public Bill committee'),
142
    7 => gettext('Scottish Parliament debate'),
143
    8 => gettext('Scottish Parliament written answer'),
144
    9 => gettext('London Mayoral question'),
145
    10 => gettext('Senedd debate'),
146
    11 => gettext('Senedd debate'),
147
    101 => gettext('Lords debate'),
148
    'F' => gettext('event'),
149
    'V' => gettext('vote'),
150
];
151
$sects_plural = [
152
    1 => gettext('Commons debates'),
153
    2 => gettext('Westminster Hall debates'),
154
    3 => gettext('Written Answers'),
155
    4 => gettext('Written Ministerial Statements'),
156
    5 => gettext('Northern Ireland Assembly debates'),
157
    6 => gettext('Public Bill committees'),
158
    7 => gettext('Scottish Parliament debates'),
159
    8 => gettext('Scottish Parliament written answers'),
160
    9 => gettext('London Mayoral questions'),
161
    10 => gettext('Senedd debates'),
162
    11 => gettext('Senedd debates'),
163
    101 => gettext('Lords debates'),
164
    'F' => gettext('events'),
165
    'V' => gettext('votes'),
166
];
167
$sects_gid = [
168
    1 => 'debate',
169
    2 => 'westminhall',
170
    3 => 'wrans',
171
    4 => 'wms',
172
    5 => 'ni',
173
    6 => 'pbc',
174
    7 => 'sp',
175
    8 => 'spwa',
176
    9 => 'london-mayors-questions',
177
    10 => 'senedd/en',
178
    11 => 'senedd/cy',
179
    101 => 'lords',
180
    'F' => 'calendar',
181
];
182
$sects_search = [
183
    1 => 'debate',
184
    2 => 'westminhall',
185
    3 => 'wrans',
186
    4 => 'wms',
187
    5 => 'ni',
188
    6 => 'pbc',
189
    7 => 'sp',
190
    8 => 'spwrans',
191
    9 => 'lmqs',
192
    10 => 'wales',
193
    11 => 'wales',
194
    101 => 'lords',
195
    'F' => 'future',
196
];
197
198
$domain = '';
199
$outof = count($alertdata);
200
$start_time = time();
201
foreach ($alertdata as $alertitem) {
202
    $active++;
203
    $email = $alertitem['email'];
204
    if ($onlyemail && $email != $onlyemail) {
205
        continue;
206
    }
207
    if ($fromemail && strcasecmp($email, $fromemail) > 0) {
208
        $fromflag = true;
209
    }
210
    if ($fromemail && !$fromflag) {
211
        continue;
212
    }
213
    if ($toemail && strcasecmp($email, $toemail) > 0) {
214
        continue;
215
    }
216
    $criteria_raw = $alertitem['criteria'];
217
    $include_votes = $alertitem['ignore_speaker_votes'] == 0;
218
    if (preg_match('#\bOR\b#', $criteria_raw)) {
219
        $criteria_raw = "($criteria_raw)";
220
    }
221
    $criteria_batch = $criteria_raw . " " . $batch_query_fragment;
222
223
    $lang = $alertitem['lang'];
224
    if (!$domain) {
225
        if ($lang == 'cy') {
226
            $domain = 'cy.theyworkforyou.com';
227
        } else {
228
            $domain = 'www.theyworkforyou.com';
229
        }
230
    }
231
232
    if ($email != $current['email']) {
233
        if ($email_text) {
234
235
            if ($banner) {
236
                $email_text = $text_banner . $email_text;
237
            }
238
239
            write_and_send_email($current, $email_text, $html_text, $template, $html_banner);
240
        }
241
        $current['email'] = $email;
242
        $current['token'] = $alertitem['alert_id'] . '-' . $alertitem['registrationtoken'];
243
        $current['lang'] = $lang;
244
        $email_text = '';
245
        $html_text = '';
246
        $q = $db->query('SELECT user_id FROM users WHERE email = :email', [
247
            ':email' => $email,
248
        ])->first();
249
        if ($q) {
250
            $user_id = $q['user_id'];
251
            $registered++;
252
        } else {
253
            $user_id = 0;
254
            $unregistered++;
255
        }
256
        mlog("\nEMAIL: $email, uid $user_id; memory usage : " . memory_get_usage() . "\n");
257
    }
258
259
    mlog("  ALERT $active/$outof QUERY $queries : Xapian query '$criteria_batch'");
260
    $start = getmicrotime();
261
    $SEARCHENGINE = new SEARCHENGINE($criteria_batch, $lang);
262
    #mlog("query_remade: " . $SEARCHENGINE->query_remade() . "\n");
263
    $args = [
264
        's' => $criteria_raw, # Note: use raw here for URLs, whereas search engine has batch
265
        'threshold' => $lastupdated, # Return everything added since last time this script was run
266
        'o' => 'c',
267
        'num' => 1000, // this is limited to 1000 in hansardlist.php anyway
268
        'pop' => 1,
269
        'e' => 1, # Don't escape ampersands
270
    ];
271
    $data = $DEBATELIST->_get_data_by_search($args);
272
    $total_results = $data['info']['total_results'];
273
    $queries++;
274
    mlog(", hits " . $total_results . ", time " . (getmicrotime() - $start) . "\n");
275
276
    # Divisions
277
    if ($include_votes && preg_match('#^speaker:(\d+)$#', $criteria_raw, $m)) {
278
        $pid = $m[1];
279
        $q = $db->query('SELECT * FROM persondivisionvotes pdv JOIN divisions USING(division_id)
280
            WHERE person_id=:person_id AND pdv.lastupdate >= :time', [
281
            'person_id' => $pid,
282
            ':time' => date('Y-m-d H:i:s', $lastupdated),
0 ignored issues
show
Bug introduced by
It seems like $lastupdated can also be of type string; however, parameter $timestamp of date() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

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

282
            ':time' => date('Y-m-d H:i:s', /** @scrutinizer ignore-type */ $lastupdated),
Loading history...
283
        ]);
284
        foreach ($q as $row) {
285
            # Skip other-language divisions if needed, set locale
286
            if (strpos($row['division_id'], '-cy-')) {
287
                if ($lang == 'en') {
288
                    continue;
289
                }
290
                change_locale('cy');
291
            } elseif (strpos($row['division_id'], '-en-')) {
292
                if ($lang == 'cy') {
293
                    continue;
294
                }
295
                change_locale('en');
296
            } else {
297
                change_locale('en');
298
            }
299
300
            $vote = $row['vote'];
301
            $num = $row['division_number'];
302
            $teller = '';
303
            if (strpos($vote, 'tell') !== false) {
304
                $teller = ', ' . gettext('as a teller');
305
                $vote = str_replace('tell', '', $vote);
306
            }
307
            if ($vote == 'absent') {
308
                continue;
309
            }
310
            if ($vote == 'aye' && $row['yes_text']) {
311
                $text = "Voted ($vote$teller) " . $row['yes_text'];
312
            } elseif ($vote == 'no' && $row['no_text']) {
313
                $text = "Voted ($vote$teller) " . $row['no_text'];
314
            } elseif ($vote == 'aye') {
315
                $text = gettext("Voted aye") . $teller;
316
            } elseif ($vote == 'no') {
317
                $text = gettext("Voted no") . $teller;
318
            } elseif ($vote == 'both') {
319
                $text = gettext("Voted abstain") . $teller;
320
            } else {
321
                $text = sprintf(gettext("Voted %s"), $vote) . $teller;
322
            }
323
            $text .= " " . sprintf(gettext("(division #%s; result was <b>%s</b> aye, <b>%s</b> no)"), $num, $row['yes_total'], $row['no_total']);
324
            $data['rows'][] = [
325
                'parent' => [
326
                    'body' => $row['division_title'],
327
                ],
328
                'extract' => $text,
329
                'listurl' => '/divisions/' . $row['division_id'],
330
                'major' => 'V',
331
                'hdate' => $row['division_date'],
332
                'hpos' => $row['division_number'],
333
            ];
334
        }
335
    }
336
337
    if (isset($data['rows']) && count($data['rows']) > 0) {
338
        usort($data['rows'], 'sort_by_stuff'); # Sort results into order, by major, then date, then hpos
339
        $o = [];
340
        $major = 0;
341
        $count = [];
342
        $total = 0;
343
        $any_content = false;
344
        foreach ($data['rows'] as $row) {
345
            if ($major !== $row['major']) {
346
                $count[$major] = $total;
347
                $total = 0;
348
                $major = $row['major'];
349
                $o[$major] = ['text' => '', 'html' => ''];
350
                $k = 3;
351
            }
352
            #mlog($row['major'] . " " . $row['gid'] ."\n");
353
354
            if (isset($sects_gid[$major])) {
355
                $q = $db->query('SELECT gid_from FROM gidredirect WHERE gid_to = :gid_to', [
356
                    ':gid_to' => 'uk.org.publicwhip/' . $sects_gid[$major] . '/' . $row['gid'],
357
                ]);
358
                if ($q->rows() > 0) {
359
                    continue;
360
                }
361
            }
362
363
            if ($major == 11) {
364
                change_locale('cy');
365
            } else {
366
                change_locale('en');
367
            }
368
369
            --$k;
370
            if ($major == 'V' || $k >= 0) {
371
                $any_content = true;
372
                $parentbody = text_html_to_email($row['parent']['body']);
373
                $body_text = text_html_to_email($row['extract']);
374
                $body_html = $row['extract'];
375
                if (isset($row['speaker']) && count($row['speaker'])) {
376
                    $body_text = $row['speaker']['name'] . ': ' . $body_text;
377
                    $body_html = '<strong style="font-weight: 900;">' . $row['speaker']['name'] . '</strong>: ' . $body_html;
378
                }
379
                $body_html = '<p style="font-size: 16px;">' . $body_html . '</p>';
380
381
                $body_text = wordwrap($body_text, 72);
382
                $o[$major]['text'] .= $parentbody . ' (' . format_date($row['hdate'], SHORTDATEFORMAT) . ")\nhttps://$domain" . $row['listurl'] . "\n";
383
                $o[$major]['text'] .= $body_text . "\n\n";
384
                $o[$major]['html'] .= '<a href="https://' . $domain . $row['listurl'] . '"><h2 style="line-height: 1.2; font-size: 17px; font-weight: 900;">' . $parentbody . '</h2></a> <span style="margin: 16px 0 0 0; display: block; font-size: 16px;">' . format_date($row['hdate'], SHORTDATEFORMAT) . '</span>';
385
                $o[$major]['html'] .= $body_html . "\n\n";
386
            }
387
            $total++;
388
        }
389
        $count[$major] = $total;
390
391
        if ($any_content) {
392
            # Add data to email_text/html_text
393
            $desc = trim(html_entity_decode($data['searchdescription']));
394
            $desc = trim(preg_replace(['#\(B\d+( OR B\d+)*\)#', '#B\d+( OR B\d+)*#'], '', $desc));
395
            foreach ($o as $major => $body) {
396
                if ($body['text']) {
397
                    $heading_text = $desc . ' : ' . $count[$major] . ' ' . ngettext($sects[$major], $sects_plural[$major], $count[$major]);
398
                    $heading_html = $desc . ' : <strong>' . $count[$major] . '</strong> ' . ngettext($sects[$major], $sects_plural[$major], $count[$major]);
399
400
                    $email_text .= "$heading_text\n" . str_repeat('=', strlen($heading_text)) . "\n\n";
401
                    if ($count[$major] > 3 && $major != 'V') {
402
                        $url = "https://$domain/search/?s=" . urlencode($criteria_raw) . "+section:" . $sects_search[$major] . "&o=d";
403
                        $email_text .= gettext('There are more results than we have shown here.') . ' ' . gettext('See more') . ":\n$url\n\n";
404
                    }
405
                    $email_text .= $body['text'];
406
407
                    $html_text .= '<hr style="height:2px;border-width:0; background-color: #f7f6f5; margin: 30px 0;">';
408
                    $html_text .= '<p style="font-size:16px;">' . $heading_html . '</p>';
409
                    if ($count[$major] > 3 && $major != 'V') {
410
                        $html_text .= '<p style="font-size:16px;">' . gettext('There are more results than we have shown here.') . ' <a href="' . $url . '">' . gettext('See more') . '</a></p>';
411
                    }
412
                    $html_text .= $body['html'];
413
                }
414
            }
415
        }
416
    }
417
}
418
if ($email_text) {
419
    if ($banner) {
420
        $email_text = $text_banner . $email_text;
421
    }
422
    write_and_send_email($current, $email_text, $html_text, $template, $html_banner);
423
}
424
425
mlog("\n");
426
427
$sss = "Active alerts: $active\nEmail lookups: $registered registered, $unregistered unregistered\nQuery lookups: $queries\nSent emails: $sentemails\n";
428
if ($globalsuccess) {
429
    $sss .= 'Everything went swimmingly, in ';
430
} else {
431
    $sss .= 'Something went wrong! Total time: ';
432
}
433
$sss .= (getmicrotime() - $global_start) . "\n\n";
434
mlog($sss);
435
436
437
438
if (!$nomail && !$onlyemail) {
439
    $fp = fopen(RAWDATA . '/alerts-lastsent', 'w');
440
    fwrite($fp, time() . "\n");
441
    fwrite($fp, $max_batch_id);
442
    fclose($fp);
443
    mail(ALERT_STATS_EMAILS, 'Email alert statistics', $sss, 'From: Email Alerts <[email protected]>');
444
445
    // Send Google Analytics event with the number of alerts sent
446
    if ($sentemails > 0) {
447
        $ga_success = send_ga_event('alerts_sent', [
448
            'email_counts' => $sentemails,
449
        ]);
450
451
        if ($ga_success) {
452
            mlog("Google Analytics event sent: $sentemails alerts\n");
453
        } else {
454
            mlog("Failed to send Google Analytics event\n");
455
        }
456
    }
457
458
}
459
mlog(date('r') . "\n");
460
461
function _sort($a, $b) {
462
    if ($a > $b) {
463
        return 1;
464
    } elseif ($a < $b) {
465
        return -1;
466
    }
467
    return 0;
468
}
469
470
function sort_by_stuff($a, $b) {
471
    # Always have votes first.
472
    if ($a['major'] == 'V' && $b['major'] != 'V') {
473
        return -1;
474
    } elseif ($b['major'] == 'V' && $a['major'] != 'V') {
475
        return 1;
476
    }
477
478
    # Always have future business second..
479
    if ($a['major'] == 'F' && $b['major'] != 'F') {
480
        return -1;
481
    } elseif ($b['major'] == 'F' && $a['major'] != 'F') {
482
        return 1;
483
    }
484
485
    # Otherwise sort firstly by major number (so Commons before NI before SP before Lords)
486
    if ($ret = _sort($a['major'], $b['major'])) {
487
        return $ret;
488
    }
489
490
    # Then by date (most recent first for everything except future, which is the opposite)
491
    if ($a['major'] == 'F') {
492
        if ($ret = _sort($a['hdate'], $b['hdate'])) {
493
            return $ret;
494
        }
495
    } else {
496
        if ($ret = _sort($b['hdate'], $a['hdate'])) {
497
            return $ret;
498
        }
499
    }
500
501
    # Lastly by speech position within a debate.
502
    if ($a['hpos'] == $b['hpos']) {
503
        return 0;
504
    }
505
    return ($a['hpos'] > $b['hpos']) ? 1 : -1;
506
}
507
508
function write_and_send_email($current, $text, $html, $template, $html_banner) {
509
    global $globalsuccess, $sentemails, $nomail, $start_time, $domain;
510
511
    $text .= '====================';
512
    $sentemails++;
513
    mlog("SEND $sentemails : Sending email to $current[email] ... ");
514
    $d = ['to' => $current['email'], 'template' => $template];
515
    $m = [
516
        'DATA' => $text,
517
        '_HTML_' => $html,
518
        '_BANNER_' => '',
519
        'MANAGE' => "https://$domain/D/" . $current['token'],
520
    ];
521
    if ($html_banner) {
522
        $m['_BANNER_'] = $html_banner;
523
    }
524
    if (!$nomail) {
525
        $success = send_template_email($d, $m, true, true, $current['lang']); # true = "Precedence: bulk", want bounces
526
        mlog("sent ... ");
527
        # sleep if time between sending mails is less than a certain number of seconds on average
528
        # 0.25 is number of seconds per mail not to be quicker than
529
        if (((time() - $start_time) / $sentemails) < 0.25) {
530
            mlog("pausing ... ");
531
            sleep(1);
532
        }
533
    } else {
534
        mlog($text);
535
        $success = 1;
536
    }
537
    mlog("done\n");
538
    if (!$success) {
539
        $globalsuccess = 0;
540
    }
541
}
542
543
function text_html_to_email($s) {
544
    $s = preg_replace('#</?(i|b|small)>#', '', $s);
545
    $s = preg_replace('#</?span[^>]*>#', '*', $s);
546
    $s = str_replace(
547
        ['&#163;', '&#8211;', '&#8212;', '&#8217;', '<br>'],
548
        ["\xa3", '-', '-', "'", "\n"],
549
        $s
550
    );
551
    return $s;
552
}
553
554
# Switch the language to that of the data/alert
555
function change_locale($lang) {
556
    if ($lang == 'cy') {
557
        setlocale(LC_ALL, 'cy_GB.UTF-8');
558
        putenv('LC_ALL=cy_GB.UTF-8');
559
    } else {
560
        setlocale(LC_ALL, 'en_GB.UTF-8');
561
        putenv('LC_ALL=en_GB.UTF-8');
562
    }
563
}
564