1<?php
2// mail.php -- HotCRP mail tool
3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE.
4
5require_once("src/initweb.php");
6require_once("src/papersearch.php");
7require_once("src/mailclasses.php");
8if (!$Me->is_manager() && !$Me->isPC)
9    $Me->escape();
10
11// load mail from log
12if (isset($Qreq->fromlog) && ctype_digit($Qreq->fromlog)
13    && $Me->privChair) {
14    $result = $Conf->qe_raw("select * from MailLog where mailId=" . $Qreq->fromlog);
15    if (($row = edb_orow($result))) {
16        foreach (["recipients", "q", "t", "cc", "replyto", "subject", "emailBody"] as $field)
17            if (isset($row->$field) && !isset($Qreq[$field]))
18                $Qreq[$field] = $row->$field;
19        if ($row->q)
20            $Qreq["plimit"] = 1;
21    }
22}
23
24// create options
25$tOpt = array();
26if ($Me->privChair) {
27    $tOpt["s"] = "Submitted papers";
28    if ($Conf->timePCViewDecision(false) && $Conf->setting("paperacc") > 0)
29        $tOpt["acc"] = "Accepted papers";
30    $tOpt["unsub"] = "Unsubmitted papers";
31    $tOpt["all"] = "All papers";
32}
33if ($Me->is_explicit_manager() || ($Me->privChair && $Conf->has_any_manager()))
34    $tOpt["manager"] = "Papers you administer";
35$tOpt["req"] = "Your review requests";
36if (!isset($Qreq->t) || !isset($tOpt[$Qreq->t]))
37    $Qreq->t = key($tOpt);
38
39// mailer
40$mailer_options = array("requester_contact" => $Me);
41$null_mailer = new HotCRPMailer($Conf, null, null, array_merge(array("width" => false), $mailer_options));
42
43// template options
44if (isset($Qreq->monreq))
45    $Qreq->template = "myreviewremind";
46if (isset($Qreq->template) && !isset($Qreq->check))
47    $Qreq->loadtmpl = -1;
48
49// paper selection
50if (!isset($Qreq->q) || trim($Qreq->q) == "(All)")
51    $Qreq->q = "";
52$Qreq->allow_a("p", "pap");
53if (!isset($Qreq->p) && isset($Qreq->pap)) // support p= and pap=
54    $Qreq->p = $Qreq->pap;
55if (isset($Qreq->p) && is_string($Qreq->p))
56    $Qreq->p = preg_split('/\s+/', $Qreq->p);
57// It's OK to just set $Qreq->p from the input without
58// validation because MailRecipients filters internally
59if (isset($Qreq->prevt) && isset($Qreq->prevq)) {
60    if (!isset($Qreq->plimit))
61        unset($Qreq->p);
62    else if (($Qreq->prevt !== $Qreq->t || $Qreq->prevq !== $Qreq->q)
63             && !isset($Qreq->psearch)) {
64        $Conf->warnMsg("You changed the paper search. Please review the paper list.");
65        $Qreq->psearch = true;
66    }
67}
68$papersel = null;
69if (isset($Qreq->p) && is_array($Qreq->p)
70    && !isset($Qreq->psearch)) {
71    $papersel = array();
72    foreach ($Qreq->p as $p)
73        if (($p = cvtint($p)) > 0)
74            $papersel[] = $p;
75    sort($papersel);
76    $Qreq->q = join(" ", $papersel);
77    $Qreq->plimit = 1;
78} else if (isset($Qreq->plimit)) {
79    $search = new PaperSearch($Me, array("t" => $Qreq->t, "q" => $Qreq->q));
80    $papersel = $search->paper_ids();
81    sort($papersel);
82} else
83    $Qreq->q = "";
84
85// Load template if requested
86if (isset($Qreq->loadtmpl)) {
87    $t = $Qreq->get("template", "genericmailtool");
88    if (!isset($mailTemplates[$t])
89        || (!isset($mailTemplates[$t]["mailtool_name"]) && !isset($mailTemplates[$t]["mailtool_priority"])))
90        $t = "genericmailtool";
91    $template = $mailTemplates[$t];
92    if (!isset($Qreq->recipients) || $Qreq->loadtmpl != -1)
93        $Qreq->recipients = get($template, "mailtool_recipients", "s");
94    if (isset($template["mailtool_search_type"]))
95        $Qreq->t = $template["mailtool_search_type"];
96    $Qreq->subject = $null_mailer->expand($template["subject"]);
97    $Qreq->emailBody = $null_mailer->expand($template["body"]);
98}
99
100// Set recipients list, now that template is loaded
101$recip = new MailRecipients($Me, $Qreq->recipients, $papersel,
102                            $Qreq->newrev_since);
103
104// Warn if no papers match
105if (isset($papersel) && count($papersel) == 0
106    && !isset($Qreq->loadtmpl) && !isset($Qreq->psearch)
107    && $recip->need_papers()) {
108    Conf::msg_error("No papers match that search.");
109    unset($papersel);
110    unset($Qreq->check, $Qreq->send);
111}
112
113if (isset($Qreq->monreq))
114    $Conf->header("Monitor external reviews", "mail");
115else
116    $Conf->header("Mail", "mail");
117
118$subjectPrefix = "[" . $Conf->short_name . "] ";
119
120
121class MailSender {
122
123    private $recip;
124    private $sending;
125    private $qreq;
126
127    private $started = false;
128    private $group;
129    private $groupable = false;
130    private $mcount = 0;
131    private $mrecipients = array();
132    private $mpapers = array();
133    private $cbcount = 0;
134    private $mailid_text = "";
135
136    function __construct($recip, $sending, Qrequest $qreq) {
137        $this->recip = $recip;
138        $this->sending = $sending;
139        $this->qreq = $qreq;
140        $this->group = $qreq->group || !$qreq->ungroup;
141    }
142
143    static function check($recip, $qreq) {
144        $ms = new MailSender($recip, false, $qreq);
145        $ms->run();
146    }
147
148    static function send($recip, $qreq) {
149        $ms = new MailSender($recip, true, $qreq);
150        $ms->run();
151    }
152
153    private function echo_actions($extra_class = "") {
154        echo '<div class="aa', $extra_class, '">',
155            Ht::submit("send", "Send", array("style" => "margin-right:4em")),
156            ' &nbsp; ';
157        $class = $this->groupable ? "" : " hidden";
158        if (!$this->qreq->group && $this->qreq->ungroup)
159            echo Ht::submit("group", "Gather recipients", ["class" => "btn mail_groupable" . $class]);
160        else
161            echo Ht::submit("ungroup", "Separate recipients", ["class" => "btn mail_groupable" . $class]);
162        echo ' &nbsp; ', Ht::submit("cancel", "Cancel"), '</div>';
163    }
164
165    private function echo_prologue() {
166        global $Conf, $Me;
167        if ($this->started)
168            return;
169        echo Ht::form(hoturl_post("mail"));
170        foreach (array("recipients", "subject", "emailBody", "cc", "replyto", "q", "t", "plimit", "newrev_since") as $x)
171            if (isset($this->qreq[$x]))
172                echo Ht::hidden($x, $this->qreq[$x]);
173        if (!$this->group)
174            echo Ht::hidden("ungroup", 1);
175        $recipients = (string) $this->qreq->recipients;
176        if ($this->sending) {
177            echo "<div id='foldmail' class='foldc fold2c'>",
178                "<div class='fn fx2 merror'>In the process of sending mail.  <strong>Do not leave this page until this message disappears!</strong><br /><span id='mailcount'></span></div>",
179                "<div id='mailwarnings'></div>",
180                "<div class='fx'><div class='confirm'>Sent to:&nbsp;", $this->recip->unparse(),
181                '<span id="mailinfo"></span></div>',
182                '<div class="aa">',
183                Ht::submit("go", "Prepare more mail"),
184                "</div></div>",
185                // This next is only displayed when Javascript is off
186                "<div class='fn2 warning'>Sending mail. <strong>Do not leave this page until it finishes rendering!</strong></div>",
187                "</div>";
188        } else {
189            if (isset($this->qreq->emailBody)
190                && $Me->privChair
191                && (strpos($this->qreq->emailBody, "%REVIEWS%")
192                    || strpos($this->qreq->emailBody, "%COMMENTS%"))) {
193                if (!$Conf->can_some_author_view_review())
194                    echo "<div class='warning'>Although these mails contain reviews and/or comments, authors can’t see reviews or comments on the site. (<a href='", hoturl("settings", "group=dec"), "' class='nw'>Change this setting</a>)</div>\n";
195                else if (!$Conf->can_some_author_view_review(true))
196                    echo "<div class='warning'>Mails to users who have not completed their own reviews will not include reviews or comments. (<a href='", hoturl("settings", "group=dec"), "' class='nw'>Change the setting</a>)</div>\n";
197            }
198            if (isset($this->qreq->emailBody)
199                && $Me->privChair
200                && substr($recipients, 0, 4) == "dec:") {
201                if (!$Conf->can_some_author_view_decision())
202                    echo "<div class='warning'>You appear to be sending an acceptance or rejection notification, but authors can’t see paper decisions on the site. (<a href='", hoturl("settings", "group=dec"), "' class='nw'>Change this setting</a>)</div>\n";
203            }
204            echo "<div id='foldmail' class='foldc fold2c'>",
205                "<div class='fn fx2 warning'>In the process of preparing mail.  You will be able to send the prepared mail once this message disappears.<br /><span id='mailcount'></span></div>",
206                "<div id='mailwarnings'></div>",
207                "<div class='fx info'>Verify that the mails look correct, then select “Send” to send the checked mails.<br />",
208                "Mailing to:&nbsp;", $this->recip->unparse(),
209                '<span id="mailinfo"></span>';
210            if (!preg_match('/\A(?:pc\z|pc:|all\z)/', $recipients)
211                && $this->qreq->plimit
212                && (string) $this->qreq->q !== "")
213                echo "<br />Paper selection:&nbsp;", htmlspecialchars($this->qreq->q);
214            echo "</div>";
215            $this->echo_actions(" fx");
216            // This next is only displayed when Javascript is off
217            echo '<div class="fn2 warning">Scroll down to send the prepared mail once the page finishes loading.</div>',
218                "</div>\n";
219        }
220        echo Ht::unstash_script("fold('mail',0,2)");
221        $this->started = true;
222    }
223
224    private function echo_mailinfo($nrows_done, $nrows_left) {
225        global $Conf;
226        if (!$this->started)
227            $this->echo_prologue();
228        $s = "\$\$('mailcount').innerHTML=\"" . round(100 * $nrows_done / max(1, $nrows_left)) . "% done.\";";
229        $m = plural($this->mcount, "mail") . ", "
230            . plural($this->mrecipients, "recipient");
231        if (count($this->mpapers) != 0)
232            $m .= ", " . plural($this->mpapers, "paper");
233        $s .= "\$\$('mailinfo').innerHTML=\"<span class='barsep'>·</span>" . $m . "\";";
234        if (!$this->sending && $this->groupable)
235            $s .= "\$('.mail_groupable').show();";
236        echo Ht::unstash_script($s);
237    }
238
239    private static function fix_body($prep) {
240        if (preg_match('^\ADear (author|reviewer)\(s\)([,;!.\s].*)\z^s', $prep->body, $m))
241            $prep->body = "Dear " . $m[1] . (count($prep->to) == 1 ? "" : "s") . $m[2];
242    }
243
244    private function send_prep($prep) {
245        global $Conf, $Me;
246
247        $cbkey = "c" . join("_", $prep->contacts) . "p" . $prep->paperId;
248        if ($this->sending && !$this->qreq[$cbkey])
249            return;
250        set_time_limit(30);
251        $this->echo_prologue();
252
253        self::fix_body($prep);
254        ++$this->mcount;
255        if ($this->sending) {
256            $prep->send();
257            foreach ($prep->contacts as $cid) {
258                // Log format matters
259                $Conf->log_for($Me, $cid, "Sent mail" . $this->mailid_text, $prep->paperId);
260            }
261        }
262
263        // hide passwords from non-chair users
264        $show_prep = $prep;
265        if (get($prep, "sensitive")) {
266            $show_prep = $prep->sensitive;
267            $show_prep->to = $prep->to;
268            self::fix_body($show_prep);
269        }
270
271        echo '<div class="mail"><table>';
272        $nprintrows = 0;
273        foreach (array("To", "cc", "bcc", "reply-to", "Subject") as $k) {
274            if ($k == "To") {
275                $vh = array();
276                foreach ($show_prep->to as $to)
277                    $vh[] = htmlspecialchars(MimeText::decode_header($to));
278                $vh = '<div style="max-width:60em"><span class="nw">' . join(',</span> <span class="nw">', $vh) . '</span></div>';
279            } else if ($k == "Subject")
280                $vh = htmlspecialchars(MimeText::decode_header($show_prep->subject));
281            else if (($line = get($show_prep->headers, $k))) {
282                $k = substr($line, 0, strlen($k));
283                $vh = htmlspecialchars(MimeText::decode_header(substr($line, strlen($k) + 2)));
284            } else
285                continue;
286            echo " <tr>";
287            if (++$nprintrows > 1)
288                echo "<td class='mhpad'></td>";
289            else if ($this->sending)
290                echo "<td class='mhx'></td>";
291            else {
292                ++$this->cbcount;
293                echo '<td class="mhcb"><input type="checkbox" class="uix js-range-click" name="', $cbkey,
294                    '" value="1" checked="checked" data-range-type="mhcb" id="psel', $this->cbcount,
295                    '" /></td>';
296            }
297            echo '<td class="mhnp nw">', $k, ":</td>",
298                '<td class="mhdp">', $vh, "</td></tr>\n";
299        }
300
301        echo " <tr><td></td><td></td><td class='mhb'><pre class='email'>",
302            Ht::link_urls(htmlspecialchars($show_prep->body)),
303            "</pre></td></tr>\n",
304            "<tr><td class='mhpad'></td><td></td><td class='mhpad'></td></tr>",
305            "</table></div>\n";
306    }
307
308    private function process_prep($prep, &$last_prep, $row) {
309        // Don't combine senders if anything differs. Also, don't combine
310        // mails from different papers, unless those mails are to the same
311        // person.
312        $mail_differs = !$prep->can_merge($last_prep);
313        $prep_to = $prep->to;
314
315        if (!$mail_differs)
316            $this->groupable = true;
317        if ($mail_differs || !$this->group) {
318            if (!$last_prep->fake)
319                $this->send_prep($last_prep);
320            $last_prep = $prep;
321            $last_prep->contacts = array();
322            $last_prep->to = array();
323        }
324
325        if ($prep->fake || isset($last_prep->contacts[$row->contactId]))
326            return false;
327        else {
328            $last_prep->contacts[$row->contactId] = $row->contactId;
329            $this->mrecipients[$row->contactId] = true;
330            $last_prep->add_recipients($prep_to);
331            return true;
332        }
333    }
334
335    private function run() {
336        global $Conf, $Me, $subjectPrefix, $mailer_options;
337
338        $subject = trim((string) $this->qreq->subject);
339        if (substr($subject, 0, strlen($subjectPrefix)) != $subjectPrefix)
340            $subject = $subjectPrefix . $subject;
341        $emailBody = $this->qreq->emailBody;
342        $template = array("subject" => $subject, "body" => $emailBody);
343        $rest = array("cc" => $this->qreq->cc, "reply-to" => $this->qreq->replyto, "no_error_quit" => true);
344        $rest = array_merge($rest, $mailer_options);
345
346        // test whether this mail is paper-sensitive
347        $mailer = new HotCRPMailer($Conf, $Me, null, $rest);
348        $prep = $mailer->make_preparation($template, $rest);
349        $paper_sensitive = preg_match('/%[A-Z0-9]+[(%]/', $prep->subject . $prep->body);
350
351        $q = $this->recip->query($paper_sensitive);
352        if (!$q)
353            return Conf::msg_error("Bad recipients value");
354        $result = $Conf->qe_raw($q);
355        if (!$result)
356            return;
357        $recipients = (string) $this->qreq->recipients;
358
359        if ($this->sending) {
360            $q = "recipients=?, cc=?, replyto=?, subject=?, emailBody=?, q=?, t=?";
361            $qv = [$recipients, $this->qreq->cc, $this->qreq->replyto, $this->qreq->subject, $this->qreq->emailBody, $this->qreq->q, $this->qreq->t];
362            if ($Conf->sversion >= 146 && !$Me->privChair)
363                $q .= ", fromNonChair=1";
364            if (($log_result = $Conf->qe_apply("insert into MailLog set $q", $qv)))
365                $this->mailid_text = " #" . $log_result->insert_id;
366            // Mail format matters
367            $Me->log_activity("Sending mail$this->mailid_text \"$subject\"");
368        } else
369            $rest["no_send"] = true;
370
371        $mailer = new HotCRPMailer($Conf);
372        $mailer->combination_type = $this->recip->combination_type($paper_sensitive);
373        $fake_prep = new HotCRPMailPreparation($Conf);
374        $fake_prep->fake = true;
375        $last_prep = $fake_prep;
376        $nrows_done = 0;
377        $nrows_left = edb_nrows($result);
378        $nwarnings = 0;
379        $preperrors = array();
380        $revinform = ($recipients == "newpcrev" ? array() : null);
381        while (($row = PaperInfo::fetch($result, $Me))) {
382            ++$nrows_done;
383
384            $contact = new Contact($row);
385            $rest["newrev_since"] = $this->recip->newrev_since;
386            $mailer->reset($contact, $row, $rest);
387            $prep = $mailer->make_preparation($template, $rest);
388
389            if ($prep->errors) {
390                foreach ($prep->errors as $lcfield => $hline) {
391                    $reqfield = ($lcfield == "reply-to" ? "replyto" : $lcfield);
392                    Ht::error_at($reqfield);
393                    $emsg = Mailer::$email_fields[$lcfield] . " destination isn’t a valid email list: <blockquote><tt>" . htmlspecialchars($hline) . "</tt></blockquote> Make sure email address are separated by commas; put names in \"quotes\" and email addresses in &lt;angle brackets&gt;.";
394                    if (!isset($preperrors[$emsg]))
395                        Conf::msg_error($emsg);
396                    $preperrors[$emsg] = true;
397                }
398            } else if ($this->process_prep($prep, $last_prep, $row)) {
399                if ((!$Me->privChair || opt("chairHidePasswords"))
400                    && !@$last_prep->sensitive) {
401                    $srest = array_merge($rest, array("sensitivity" => "display"));
402                    $mailer->reset($contact, $row, $srest);
403                    $last_prep->sensitive = $mailer->make_preparation($template, $srest);
404                }
405            }
406
407            if ($nwarnings != $mailer->nwarnings() || $nrows_done % 5 == 0)
408                $this->echo_mailinfo($nrows_done, $nrows_left);
409            if ($nwarnings != $mailer->nwarnings()) {
410                $this->echo_prologue();
411                $nwarnings = $mailer->nwarnings();
412                echo "<div id='foldmailwarn$nwarnings' class='hidden'><div class='warning'>", join("<br />", $mailer->warnings()), "</div></div>";
413                echo Ht::unstash_script("\$\$('mailwarnings').innerHTML = \$\$('foldmailwarn$nwarnings').innerHTML;");
414            }
415
416            if ($this->sending && $revinform !== null)
417                $revinform[] = "(paperId=$row->paperId and contactId=$row->contactId)";
418        }
419
420        $this->process_prep($fake_prep, $last_prep, (object) array("paperId" => -1));
421        $this->echo_mailinfo($nrows_done, $nrows_left);
422
423        if (!$this->started && !count($preperrors))
424            return Conf::msg_error("No users match “" . $this->recip->unparse() . "” for that search.");
425        else if (!$this->started)
426            return false;
427        else if (!$this->sending)
428            $this->echo_actions();
429        if ($revinform)
430            $Conf->qe_raw("update PaperReview set timeRequestNotified=" . time() . " where " . join(" or ", $revinform));
431        echo "</form>";
432        echo Ht::unstash_script("fold('mail', null);");
433        $Conf->footer();
434        exit;
435    }
436
437}
438
439
440// Set subject and body if necessary
441if (!isset($Qreq->subject))
442    $Qreq->subject = $null_mailer->expand($mailTemplates["genericmailtool"]["subject"]);
443if (!isset($Qreq->emailBody))
444    $Qreq->emailBody = $null_mailer->expand($mailTemplates["genericmailtool"]["body"]);
445if (substr($Qreq->subject, 0, strlen($subjectPrefix)) == $subjectPrefix)
446    $Qreq->subject = substr($Qreq->subject, strlen($subjectPrefix));
447if (isset($Qreq->cc) && $Me->is_manager()) // XXX should only apply to papers you administer
448    $Qreq->cc = simplify_whitespace($Qreq->cc);
449else if ($Conf->opt("emailCc"))
450    $Qreq->cc = $Conf->opt("emailCc");
451else
452    $Qreq->cc = Text::user_email_to($Conf->site_contact());
453if (isset($Qreq->replyto) && $Me->is_manager()) // XXX should only apply to papers you administer
454    $Qreq->replyto = simplify_whitespace($Qreq->replyto);
455else
456    $Qreq->replyto = $Conf->opt("emailReplyTo", "");
457
458
459// Check or send
460if (!$Qreq->loadtmpl && !$Qreq->cancel && !$Qreq->psearch && !$recip->error && $Qreq->post_ok()) {
461    if ($Qreq->send)
462        MailSender::send($recip, $Qreq);
463    else if ($Qreq->check || $Qreq->group || $Qreq->ungroup)
464        MailSender::check($recip, $Qreq);
465}
466
467
468if (isset($Qreq->monreq)) {
469    $plist = new PaperList(new PaperSearch($Me, ["t" => "req", "q" => ""]), ["foldable" => true]);
470    $plist->set_table_id_class("foldpl", "pltable_full");
471    $ptext = $plist->table_html("reqrevs", ["header_links" => true, "list" => true]);
472    if ($plist->count == 0)
473        $Conf->infoMsg("You have not requested any external reviews.  <a href='" . hoturl("index") . "'>Return home</a>");
474    else {
475        echo "<h2>Requested reviews</h2>\n\n", $ptext, '<div class="info">';
476        if ($plist->has("need_review"))
477            echo "Some of your requested external reviewers have not completed their reviews.  To send them an email reminder, check the text below and then select &ldquo;Prepare mail.&rdquo;  You’ll get a chance to review the emails and select specific reviewers to remind.";
478        else
479            echo "All of your requested external reviewers have completed their reviews.  <a href='", hoturl("index"), "'>Return home</a>";
480        echo "</div>\n";
481    }
482    if (!$plist->has("need_review")) {
483        $Conf->footer();
484        exit;
485    }
486}
487
488echo Ht::form(hoturl_post("mail", "check=1")),
489    Ht::hidden_default_submit("default", 1), "
490
491<div class='aa' style='padding-left:8px'>
492  <strong>Template:</strong> &nbsp;";
493$tmpl = array();
494foreach ($mailTemplates as $k => $v) {
495    if (isset($v["mailtool_name"])
496        && ($Me->privChair || defval($v, "mailtool_pc")))
497        $tmpl[$k] = defval($v, "mailtool_priority", 100);
498}
499asort($tmpl);
500foreach ($tmpl as $k => &$v) {
501    $v = $mailTemplates[$k]["mailtool_name"];
502}
503if (!isset($Qreq->template) || !isset($tmpl[$Qreq->template]))
504    $Qreq->template = "genericmailtool";
505echo Ht::select("template", $tmpl, $Qreq->template),
506    " &nbsp;",
507    Ht::submit("loadtmpl", "Load", ["id" => "loadtmpl"]),
508    " &nbsp;
509 <span class='hint'>Templates are mail texts tailored for common conference tasks.</span>
510</div>
511
512<div class='mail' style='float:left;margin:4px 1em 12px 0'><table id=\"foldpsel\" class=\"fold8c fold9o fold10c\">\n";
513
514// ** TO
515echo '<tr><td class="mhnp nw"><label for="recipients">To:</label></td><td class="mhdd">',
516    $recip->selectors(),
517    "<div class='g'></div>\n";
518
519// paper selection
520echo '<table class="fx9"><tr>';
521if ($Me->privChair)
522    echo '<td class="nw">',
523        Ht::checkbox("plimit", 1, isset($Qreq->plimit), ["id" => "plimit"]),
524        "&nbsp;</td><td>", Ht::label("Choose papers", "plimit"),
525        "<span class='fx8'>:&nbsp; ";
526else
527    echo '<td class="nw">Papers: &nbsp;</td><td>',
528        Ht::hidden("plimit", 1), '<span>';
529echo Ht::entry("q", (string) $Qreq->q,
530               array("id" => "q", "placeholder" => "(All)",
531                     "class" => "papersearch", "size" => 36)),
532    " &nbsp;in&nbsp;";
533if (count($tOpt) == 1)
534    echo htmlspecialchars($tOpt[$Qreq->t]);
535else
536    echo " ", Ht::select("t", $tOpt, $Qreq->t, array("id" => "t"));
537echo " &nbsp;", Ht::submit("psearch", "Search");
538echo "</span>";
539if (isset($Qreq->plimit)
540    && !isset($Qreq->monreq)
541    && (isset($Qreq->loadtmpl) || isset($Qreq->psearch))) {
542    $plist = new PaperList(new PaperSearch($Me, ["t" => $Qreq->t, "q" => $Qreq->q]));
543    $ptext = $plist->table_html("reviewers", ["noheader" => true, "nofooter" => true]);
544    echo "<div class='fx8'>";
545    if ($plist->count == 0)
546        echo "No papers match that search.";
547    else
548        echo '<div class="g"></div>', $ptext;
549    echo '</div>', Ht::hidden("prevt", $Qreq->t),
550        Ht::hidden("prevq", $Qreq->q);
551}
552echo "</td></tr></table>\n";
553
554echo '<div class="fx10" style="margin-top:0.35em">';
555if (!$Qreq->newrev_since && ($t = $Conf->setting("pcrev_informtime")))
556    $Qreq->newrev_since = $Conf->parseableTime($t, true);
557echo 'Assignments since:&nbsp; ',
558    Ht::entry("newrev_since", $Qreq->newrev_since,
559              array("placeholder" => "(all)", "size" => 30)),
560    '</div>';
561
562echo '<div class="fx9 g"></div>';
563
564Ht::stash_script('function mail_recipients_fold(event) {
565    var plimit = $$("plimit");
566    foldup.call(this, null, {f: !!plimit && !plimit.checked, n: 8});
567    var sopt = $(this).find("option[value=\'" + this.value + "\']");
568    foldup.call(this, null, {f: sopt.hasClass("mail-want-no-papers"), n: 9});
569    foldup.call(this, null, {f: !sopt.hasClass("mail-want-since"), n: 10});
570}
571$("#recipients, #plimit").on("change", mail_recipients_fold);
572$(function () { $("#recipients").trigger("change"); })');
573
574echo "</td></tr>\n";
575
576// ** CC, REPLY-TO
577if ($Me->is_manager()) {
578    foreach (Mailer::$email_fields as $lcfield => $field)
579        if ($lcfield !== "to" && $lcfield !== "bcc") {
580            $xfield = ($lcfield == "reply-to" ? "replyto" : $lcfield);
581            $ec = Ht::control_class($xfield);
582            echo "  <tr><td class=\"mhnp nw$ec\"><label for=\"$xfield\">$field:</label></td><td class='mhdp'>",
583                Ht::entry($xfield, $Qreq[$xfield], ["size" => 64, "class" => "textlite-tt$ec", "id" => $xfield]),
584                ($xfield == "replyto" ? "<div class='g'></div>" : ""),
585                "</td></tr>\n\n";
586        }
587}
588
589// ** SUBJECT
590echo "  <tr><td class='mhnp nw'><label for=\"subject\">Subject:</label></td><td class='mhdp'>",
591    "<tt>[", htmlspecialchars($Conf->short_name), "]&nbsp;</tt>",
592    Ht::entry("subject", $Qreq->subject, ["size" => 64, "class" => Ht::control_class("subject", "textlite-tt"), "id" => "subject"]),
593    "</td></tr>
594
595 <tr><td></td><td class='mhb'>\n",
596    Ht::textarea("emailBody", $Qreq->emailBody,
597            array("class" => "tt", "rows" => 20, "cols" => 80, "spellcheck" => "true")),
598    "</td></tr>
599</table></div>\n\n";
600
601
602if ($Me->privChair) {
603    $result = $Conf->qe_raw("select mailId, subject, emailBody from MailLog where fromNonChair=0 order by mailId desc limit 200");
604    if (edb_nrows($result)) {
605        echo "<div style='padding-top:12px;max-height:24em;overflow-y:auto'>",
606            "<strong>Recent mails:</strong>\n";
607        $i = 1;
608        while (($row = edb_orow($result))) {
609            echo "<div class='mhdd'><div style='position:relative;overflow:hidden'>",
610                "<div style='position:absolute;white-space:nowrap'><span style='min-width:2em;text-align:right;display:inline-block' class='dim'>$i.</span> <a class='q' href=\"", hoturl("mail", "fromlog=" . $row->mailId), "\">", htmlspecialchars($row->subject), " &ndash; <span class='dim'>", htmlspecialchars(UnicodeHelper::utf8_prefix($row->emailBody, 100)), "</span></a></div>",
611                "<br /></div></div>\n";
612            ++$i;
613        }
614        echo "</div>\n\n";
615    }
616}
617
618
619echo "<div class='aa' style='clear:both'>\n",
620    Ht::submit("Prepare mail"), " &nbsp; <span class='hint'>You’ll be able to review the mails before they are sent.</span>
621</div>
622
623
624<div id='mailref'>Keywords enclosed in percent signs, such as <code>%NAME%</code> or <code>%REVIEWDEADLINE%</code>, are expanded for each mail.  Use the following syntax:
625<div class='g'></div>
626<div class=\"ctable\">
627<dl class=\"ctelt\" style=\"padding-bottom:12px\">
628<dt><code>%URL%</code></dt>
629    <dd>Site URL.</dd>
630<dt><code>%LOGINURL%</code></dt>
631    <dd>URL for recipient to log in to the site.</dd>
632<dt><code>%NUMSUBMITTED%</code></dt>
633    <dd>Number of papers submitted.</dd>
634<dt><code>%NUMACCEPTED%</code></dt>
635    <dd>Number of papers accepted.</dd>
636<dt><code>%NAME%</code></dt>
637    <dd>Full name of recipient.</dd>
638<dt><code>%FIRST%</code>, <code>%LAST%</code></dt>
639    <dd>First and last names, if any, of recipient.</dd>
640<dt><code>%EMAIL%</code></dt>
641    <dd>Email address of recipient.</dd>
642<dt><code>%REVIEWDEADLINE%</code></dt>
643    <dd>Reviewing deadline appropriate for recipient.</dd>
644</dl><dl class=\"ctelt\" style=\"padding-bottom:12px\">
645<dt><code>%NUMBER%</code></dt>
646    <dd>Paper number relevant for mail.</dd>
647<dt><code>%TITLE%</code></dt>
648    <dd>Paper title.</dd>
649<dt><code>%TITLEHINT%</code></dt>
650    <dd>First couple words of paper title (useful for mail subject).</dd>
651<dt><code>%OPT(AUTHORS)%</code></dt>
652    <dd>Paper authors (if recipient is allowed to see the authors).</dd>
653</dl><dl class=\"ctelt\" style=\"padding-bottom:12px\">
654<dt><code>%REVIEWS%</code></dt>
655    <dd>Pretty-printed paper reviews.</dd>
656<dt><code>%COMMENTS%</code></dt>
657    <dd>Pretty-printed paper comments, if any.</dd>
658<dt><code>%COMMENTS(<i>tag</i>)%</code></dt>
659    <dd>Comments tagged #<code><i>tag</i></code>, if any.</dd>
660</dl><dl class=\"ctelt\" style=\"padding-bottom:12px\">
661<dt><code>%IF(SHEPHERD)%...%ENDIF%</code></dt>
662    <dd>Include text if a shepherd is assigned.</dd>
663<dt><code>%SHEPHERD%</code></dt>
664    <dd>Shepherd name and email, if any.</dd>
665<dt><code>%SHEPHERDNAME%</code></dt>
666    <dd>Shepherd name, if any.</dd>
667<dt><code>%SHEPHERDEMAIL%</code></dt>
668    <dd>Shepherd email, if any.</dd>
669</dl><dl class=\"ctelt\" style=\"padding-bottom:12px\">
670<dt><code>%IF(#<i>tag</i>)%...%ENDIF%</code></dt>
671    <dd>Include text if paper has tag <code><i>tag</i></code>.</dd>
672<dt><code>%TAGVALUE(<i>tag</i>)%</code></dt>
673    <dd>Value of paper’s <code><i>tag</i></code>.</dd>
674</dl>
675</div></div>
676
677</form>\n";
678
679$Conf->footer();
680