1<?php 2// offline.php -- HotCRP offline review management page 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5require_once("src/initweb.php"); 6if (!$Me->email) 7 $Me->escape(); 8$rf = $Conf->review_form(); 9 10 11// general error messages 12if ($Qreq->post && $Qreq->post_empty()) 13 $Conf->post_missing_msg(); 14 15 16// download blank review form action 17if (isset($Qreq->downloadForm)) { 18 $text = $rf->textFormHeader("blank") . $rf->textForm(null, null, $Me, null) . "\n"; 19 downloadText($text, "review"); 20} 21 22 23// upload review form action 24if (isset($Qreq->uploadForm) 25 && $Qreq->has_file("uploadedFile") 26 && $Qreq->post_ok()) { 27 $tf = ReviewValues::make_text($rf, $Qreq->file_contents("uploadedFile"), 28 $Qreq->file_filename("uploadedFile")); 29 while ($tf->parse_text($Qreq->override)) 30 $tf->check_and_save($Me, null, null); 31 $tf->report(); 32 // Uploading forms may have completed the reviewer's task; recheck roles. 33 Contact::update_rights(); 34} else if (isset($Qreq->uploadForm)) 35 Conf::msg_error("Choose a file first."); 36 37 38// upload tag indexes action 39function saveTagIndexes($tag, $filename, &$settings, &$titles, &$linenos, &$errors) { 40 global $Conf, $Me; 41 42 foreach ($Me->paper_set(array_keys($settings)) as $row) { 43 if ($settings[$row->paperId] !== null 44 && !$Me->can_change_tag($row, $tag, null, 1)) { 45 $errors[$linenos[$row->paperId]] = "You cannot rank paper #$row->paperId."; 46 unset($settings[$row->paperId]); 47 } else if ($titles[$row->paperId] !== "" 48 && strcmp($row->title, $titles[$row->paperId]) != 0 49 && strcasecmp($row->title, simplify_whitespace($titles[$row->paperId])) != 0) 50 $errors[$linenos[$row->paperId]] = "Warning: Title doesn’t match."; 51 } 52 53 if (!$tag) 54 $errors["0tag"] = "Tag missing."; 55 else if (count($settings)) { 56 $x = array("paper,tag,lineno"); 57 foreach ($settings as $pid => $value) 58 $x[] = "$pid,$tag#" . ($value === null ? "clear" : $value) . "," . $linenos[$pid]; 59 $assigner = new AssignmentSet($Me); 60 $assigner->parse(join("\n", $x) . "\n", $filename); 61 $assigner->report_errors(); 62 $assigner->execute(); 63 } 64 65 $settings = $titles = $linenos = array(); 66} 67 68function check_tag_index_line(&$line) { 69 if ($line && count($line) >= 2 70 && preg_match('/\A\s*(|[Xx=]|>*|\(?([-+]?\d+)\)?)\s*\z/', $line[0], $m1) 71 && preg_match('/\A\s*(\d+)\s*\z/', $line[1], $m2)) { 72 $line[0] = isset($m1[2]) && $m1[2] !== "" ? $m1[2] : $m1[1]; 73 $line[1] = $m2[1]; 74 return true; 75 } else 76 return false; 77} 78 79function setTagIndexes($qreq) { 80 global $Conf, $Me; 81 $filename = null; 82 if (isset($qreq->upload) && $qreq->has_file("file")) { 83 if (($text = $qreq->file_contents("file")) === false) { 84 Conf::msg_error("Internal error: cannot read file."); 85 return; 86 } 87 $filename = $qreq->file_filename("file"); 88 } else if (!($text = $qreq->data)) { 89 Conf::msg_error("Choose a file first."); 90 return; 91 } 92 93 $RealMe = $Me; 94 $tagger = new Tagger; 95 if (($tag = $qreq->tag)) 96 $tag = $tagger->check($tag, Tagger::NOVALUE); 97 $curIndex = 0; 98 $lineno = 1; 99 $settings = $titles = $linenos = $errors = array(); 100 $csvp = new CsvParser("", CsvParser::TYPE_GUESS); 101 foreach (explode("\n", rtrim(cleannl($text))) as $l) { 102 if (substr($l, 0, 4) == "Tag:" || substr($l, 0, 6) == "# Tag:") { 103 if (!$tag) 104 $tag = $tagger->check(trim(substr($l, ($l[0] == "#" ? 6 : 4))), Tagger::NOVALUE); 105 } else if (trim($l) !== "" && $l[0] !== "#") { 106 $csvp->unshift($l); 107 $line = $csvp->next(); 108 if ($line && check_tag_index_line($line)) { 109 if (isset($settings[$line[1]])) 110 $errors[$lineno] = "Paper #$line[1] already given on line " . $linenos[$line[1]]; 111 if ($line[0] === "X" || $line[0] === "x") 112 $settings[$line[1]] = null; 113 else if ($line[0] === "" || $line[0] === ">") 114 $settings[$line[1]] = $curIndex = $curIndex + 1; 115 else if (is_numeric($line[0])) 116 $settings[$line[1]] = $curIndex = intval($line[0]); 117 else if ($line[0] === "=") 118 $settings[$line[1]] = $curIndex; 119 else 120 $settings[$line[1]] = $curIndex = $curIndex + strlen($line[0]); 121 $titles[$line[1]] = trim(get($line, 2, "")); 122 $linenos[$line[1]] = $lineno; 123 } else 124 $errors[$lineno] = "Syntax error"; 125 } 126 ++$lineno; 127 } 128 129 if (count($settings) && $Me) 130 saveTagIndexes($tag, $filename, $settings, $titles, $linenos, $errors); 131 $Me = $RealMe; 132 133 if (count($errors)) { 134 ksort($errors); 135 foreach ($errors as $lineno => &$error) { 136 if ($filename && $lineno) 137 $error = '<span class="lineno">' . htmlspecialchars($filename) . ':' . $lineno . ':</span> ' . $error; 138 else if ($filename) 139 $error = '<span class="lineno">' . htmlspecialchars($filename) . ':</span> ' . $error; 140 } 141 Conf::msg_error('<div class="parseerr"><p>' . join("</p>\n<p>", $errors) . '</p></div>'); 142 } else if (isset($qreq->setvote)) { 143 $Conf->confirmMsg("Votes saved."); 144 } else { 145 $dtag = $tagger->unparse($tag); 146 $Conf->confirmMsg("Ranking saved. To view it, <a href='" . hoturl("search", "q=" . urlencode("editsort:#{$dtag}")) . "'>search for “editsort:#{$dtag}”</a>."); 147 } 148} 149if ((isset($Qreq->setvote) || isset($Qreq->setrank)) 150 && $Me->is_reviewer() 151 && $Qreq->post_ok()) 152 setTagIndexes($Qreq); 153 154 155$pastDeadline = !$Conf->time_review(null, $Me->isPC, true); 156 157if (!$Conf->time_review_open() && !$Me->privChair) { 158 Conf::msg_error("The site is not open for review."); 159 go(hoturl("index")); 160} 161 162$Conf->header("Offline reviewing", "offline"); 163 164if ($Me->is_reviewer()) { 165 if (!$Conf->time_review_open()) 166 $Conf->infoMsg("The site is not open for review."); 167 $Conf->infoMsg("Use this page to download a blank review form, or to upload review forms you’ve already filled out."); 168 if (!$Me->can_clickthrough("review")) { 169 echo '<div class="js-clickthrough-container">'; 170 PaperTable::echo_review_clickthrough(); 171 echo '</div>'; 172 } 173} else 174 $Conf->infoMsg("You aren’t registered as a reviewer or PC member for this conference, but for your information, you may download the review form anyway."); 175 176 177echo "<table id='offlineform'>"; 178 179// Review forms 180echo "<tr><td><h3>Download forms</h3>\n<div>"; 181if ($Me->is_reviewer()) { 182 echo "<a href='", hoturl("search", "fn=get&getfn=revform&q=&t=r&p=all"), "'>Your reviews</a><br />\n"; 183 if ($Me->has_outstanding_review()) 184 echo "<a href='", hoturl("search", "fn=get&getfn=revform&q=&t=rout&p=all"), "'>Your incomplete reviews</a><br />\n"; 185 echo "<a href='", hoturl("offline", "downloadForm=1"), "'>Blank form</a></div> 186<div class='g'></div> 187<span class='hint'><strong>Tip:</strong> Use <a href='", hoturl("search", "q="), "'>Search</a> > Download to choose individual papers.\n"; 188} else 189 echo "<a href='", hoturl("offline", "downloadForm=1"), "'>Blank form</a></div>\n"; 190echo "</td>\n"; 191if ($Me->is_reviewer()) { 192 $disabled = ($pastDeadline && !$Me->privChair ? " disabled='disabled'" : ""); 193 echo "<td><h3>Upload filled-out forms</h3>\n", 194 Ht::form(hoturl_post("offline", "uploadForm=1")), 195 Ht::hidden("postnonempty", 1), 196 "<input type='file' name='uploadedFile' accept='text/plain' size='30' $disabled/> ", 197 Ht::submit("Go", array("disabled" => !!$disabled)); 198 if ($pastDeadline && $Me->privChair) 199 echo "<br />", Ht::checkbox("override"), " ", Ht::label("Override deadlines"); 200 echo "<br /><span class='hint'><strong>Tip:</strong> You may upload a file containing several forms.</span>"; 201 echo "</form></td>\n"; 202} 203echo "</tr>\n"; 204 205 206// Ranks 207if ($Conf->setting("tag_rank") && $Me->is_reviewer()) { 208 $ranktag = $Conf->setting_data("tag_rank"); 209 echo "<tr><td><div class='g'></div></td></tr>\n", 210 "<tr><td><h3>Download ranking file</h3>\n<div>"; 211 echo "<a href=\"", hoturl("search", "fn=get&getfn=rank&tag=%7E$ranktag&q=&t=r&p=all"), "\">Your reviews</a>"; 212 if ($Me->isPC) 213 echo "<br />\n<a href=\"", hoturl("search", "fn=get&getfn=rank&tag=%7E$ranktag&q=&t=s&p=all"), "\">All submitted papers</a>"; 214 echo "</div></td>\n"; 215 216 $disabled = ($pastDeadline && !$Me->privChair ? " disabled='disabled'" : ""); 217 echo "<td><h3>Upload ranking file</h3>\n", 218 Ht::form(hoturl_post("offline", "setrank=1&tag=%7E$ranktag")), 219 Ht::hidden("upload", 1), 220 "<input type='file' name='file' accept='text/plain' size='30' $disabled/> ", 221 Ht::submit("Go", array("disabled" => !!$disabled)); 222 if ($pastDeadline && $Me->privChair) 223 echo "<br />", Ht::checkbox("override"), " ", Ht::label("Override deadlines"); 224 echo "<br /><span class='hint'><strong>Tip:</strong> Use “<a href='", hoturl("search", "q=" . urlencode("editsort:#~$ranktag")), "'>editsort:#~$ranktag</a>” to drag and drop your ranking.</span>"; 225 echo "<br /><span class='hint'><strong>Tip:</strong> “<a href='", hoturl("search", "q=order:%7E$ranktag"), "'>order:~$ranktag</a>” searches by your ranking.</span>"; 226 echo "</form></td>\n"; 227 echo "</tr>\n"; 228} 229 230echo "</table>\n"; 231 232 233$Conf->footer(); 234