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 ' '; 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 ' ', 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: ", $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: ", $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: ", 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 <angle brackets>."; 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 “Prepare mail.” 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> "; 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 " ", 507 Ht::submit("loadtmpl", "Load", ["id" => "loadtmpl"]), 508 " 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 " </td><td>", Ht::label("Choose papers", "plimit"), 525 "<span class='fx8'>: "; 526else 527 echo '<td class="nw">Papers: </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 " in "; 533if (count($tOpt) == 1) 534 echo htmlspecialchars($tOpt[$Qreq->t]); 535else 536 echo " ", Ht::select("t", $tOpt, $Qreq->t, array("id" => "t")); 537echo " ", 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: ', 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), "] </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), " – <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"), " <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