Issues (404)

scripts/alertmailer.php (1 issue)

Labels
Severity
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
    if (preg_match('#\bOR\b#', $criteria_raw)) {
218
        $criteria_raw = "($criteria_raw)";
219
    }
220
    $criteria_batch = $criteria_raw . " " . $batch_query_fragment;
221
222
    $lang = $alertitem['lang'];
223
    if (!$domain) {
224
        if ($lang == 'cy') {
225
            $domain = 'cy.theyworkforyou.com';
226
        } else {
227
            $domain = 'www.theyworkforyou.com';
228
        }
229
    }
230
231
    if ($email != $current['email']) {
232
        if ($email_text) {
233
234
            if ($banner) {
235
                $email_text = $text_banner . $email_text;
236
            }
237
238
            write_and_send_email($current, $email_text, $html_text, $template, $html_banner);
239
        }
240
        $current['email'] = $email;
241
        $current['token'] = $alertitem['alert_id'] . '-' . $alertitem['registrationtoken'];
242
        $current['lang'] = $lang;
243
        $email_text = '';
244
        $html_text = '';
245
        $q = $db->query('SELECT user_id FROM users WHERE email = :email', [
246
            ':email' => $email,
247
        ])->first();
248
        if ($q) {
249
            $user_id = $q['user_id'];
250
            $registered++;
251
        } else {
252
            $user_id = 0;
253
            $unregistered++;
254
        }
255
        mlog("\nEMAIL: $email, uid $user_id; memory usage : " . memory_get_usage() . "\n");
256
    }
257
258
    mlog("  ALERT $active/$outof QUERY $queries : Xapian query '$criteria_batch'");
259
    $start = getmicrotime();
260
    $SEARCHENGINE = new SEARCHENGINE($criteria_batch, $lang);
261
    #mlog("query_remade: " . $SEARCHENGINE->query_remade() . "\n");
262
    $args = [
263
        's' => $criteria_raw, # Note: use raw here for URLs, whereas search engine has batch
264
        'threshold' => $lastupdated, # Return everything added since last time this script was run
265
        'o' => 'c',
266
        'num' => 1000, // this is limited to 1000 in hansardlist.php anyway
267
        'pop' => 1,
268
        'e' => 1, # Don't escape ampersands
269
    ];
270
    $data = $DEBATELIST->_get_data_by_search($args);
271
    $total_results = $data['info']['total_results'];
272
    $queries++;
273
    mlog(", hits " . $total_results . ", time " . (getmicrotime() - $start) . "\n");
274
275
    # Divisions
276
    if (preg_match('#^speaker:(\d+)$#', $criteria_raw, $m)) {
277
        $pid = $m[1];
278
        $q = $db->query('SELECT * FROM persondivisionvotes pdv JOIN divisions USING(division_id)
279
            WHERE person_id=:person_id AND pdv.lastupdate >= :time', [
280
            'person_id' => $pid,
281
            ':time' => date('Y-m-d H:i:s', $lastupdated),
0 ignored issues
show
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

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