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&amp;getfn=revform&amp;q=&amp;t=r&amp;p=all"), "'>Your reviews</a><br />\n";
183    if ($Me->has_outstanding_review())
184        echo "<a href='", hoturl("search", "fn=get&amp;getfn=revform&amp;q=&amp;t=rout&amp;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> &gt; 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/>&nbsp; ",
197        Ht::submit("Go", array("disabled" => !!$disabled));
198    if ($pastDeadline && $Me->privChair)
199        echo "<br />", Ht::checkbox("override"), "&nbsp;", Ht::label("Override&nbsp;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&amp;getfn=rank&amp;tag=%7E$ranktag&amp;q=&amp;t=r&amp;p=all"), "\">Your reviews</a>";
212    if ($Me->isPC)
213        echo "<br />\n<a href=\"", hoturl("search", "fn=get&amp;getfn=rank&amp;tag=%7E$ranktag&amp;q=&amp;t=s&amp;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&amp;tag=%7E$ranktag")),
219        Ht::hidden("upload", 1),
220        "<input type='file' name='file' accept='text/plain' size='30' $disabled/>&nbsp; ",
221        Ht::submit("Go", array("disabled" => !!$disabled));
222    if ($pastDeadline && $Me->privChair)
223        echo "<br />", Ht::checkbox("override"), "&nbsp;", Ht::label("Override&nbsp;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