1<?php
2// listactions/la_get_rev.php -- HotCRP helper classes for list actions
3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE.
4
5class GetPcassignments_ListAction extends ListAction {
6    function allow(Contact $user) {
7        return $user->is_manager();
8    }
9    function run(Contact $user, $qreq, $ssel) {
10        list($header, $items) = ListAction::pcassignments_csv_data($user, $ssel->selection());
11        return $user->conf->make_csvg("pcassignments")->select($header)->add($items);
12    }
13}
14
15class GetReviewBase_ListAction extends ListAction {
16    protected $isform;
17    protected $iszip;
18    function __construct($isform, $iszip) {
19        $this->isform = $isform;
20        $this->iszip = $iszip;
21    }
22    protected function finish(Contact $user, $texts, $errors) {
23        uksort($errors, "strnatcmp");
24
25        if (empty($texts)) {
26            if (empty($errors))
27                Conf::msg_error("No papers selected.");
28            else {
29                $errors = array_map("htmlspecialchars", array_keys($errors));
30                Conf::msg_error(join("<br>", $errors) . "<br>Nothing to download.");
31            }
32            return;
33        }
34
35        $warnings = array();
36        $nerrors = 0;
37        foreach ($errors as $ee => $iserror) {
38            $warnings[] = $ee;
39            if ($iserror)
40                $nerrors++;
41        }
42        if ($nerrors)
43            array_unshift($warnings, "Some " . ($this->isform ? "review forms" : "reviews") . " are missing:");
44
45        if ($this->isform && (count($texts) == 1 || $this->iszip))
46            $rfname = "review";
47        else
48            $rfname = "reviews";
49        if (count($texts) == 1 && !$this->iszip)
50            $rfname .= key($texts);
51
52        if ($this->isform)
53            $header = $user->conf->review_form()->textFormHeader(count($texts) > 1 && !$this->iszip);
54        else
55            $header = "";
56
57        if (!$this->iszip) {
58            $text = $header;
59            if (!empty($warnings) && $this->isform) {
60                foreach ($warnings as $w)
61                    $text .= prefix_word_wrap("==-== ", $w, "==-== ");
62                $text .= "\n";
63            } else if (!empty($warnings))
64                $text .= join("\n", $warnings) . "\n\n";
65            $text .= join("", $texts);
66            downloadText($text, $rfname);
67        } else {
68            $zip = new ZipDocument($user->conf->download_prefix . "reviews.zip");
69            $zip->warnings = $warnings;
70            foreach ($texts as $pid => $text)
71                $zip->add_as($header . $text, $user->conf->download_prefix . $rfname . $pid . ".txt");
72            $result = $zip->download();
73            if (!$result->error)
74                exit;
75        }
76    }
77}
78
79class GetReviewForm_ListAction extends GetReviewBase_ListAction {
80    function __construct($conf, $fj) {
81        parent::__construct(true, $fj->name === "get/revformz");
82    }
83    function allow(Contact $user) {
84        return $user->is_reviewer();
85    }
86    function run(Contact $user, $qreq, $ssel) {
87        $rf = $user->conf->review_form();
88        if ($ssel->is_empty()) {
89            // blank form
90            $text = $rf->textFormHeader("blank") . $rf->textForm(null, null, $user, null) . "\n";
91            downloadText($text, "review");
92            return;
93        }
94
95        $texts = $errors = [];
96        foreach ($user->paper_set($ssel) as $prow) {
97            $whyNot = $user->perm_review($prow, null);
98            if ($whyNot && !isset($whyNot["deadline"])
99                && !isset($whyNot["reviewNotAssigned"]))
100                $errors[whyNotText($whyNot, true)] = true;
101            else {
102                if ($whyNot) {
103                    $t = whyNotText($whyNot, true);
104                    $errors[$t] = false;
105                    if (!isset($whyNot["deadline"]))
106                        defappend($texts[$prow->paperId], prefix_word_wrap("==-== ", strtoupper($t) . "\n\n", "==-== "));
107                }
108                foreach ($prow->full_reviews_of_user($user) as $rrow)
109                    defappend($texts[$prow->paperId], $rf->textForm($prow, $rrow, $user, null) . "\n");
110            }
111        }
112
113        $this->finish($user, $ssel->reorder($texts), $errors);
114    }
115}
116
117class GetReviews_ListAction extends GetReviewBase_ListAction {
118    private $include_paper;
119    function __construct($conf, $fj) {
120        parent::__construct(false, !!get($fj, "zip"));
121        $this->include_paper = !!get($fj, "abstract");
122        require_once("la_get_sub.php");
123    }
124    function allow(Contact $user) {
125        return $user->can_view_some_review();
126    }
127    function run(Contact $user, $qreq, $ssel) {
128        $rf = $user->conf->review_form();
129        $overrides = $user->add_overrides(Contact::OVERRIDE_CONFLICT);
130        $errors = $texts = [];
131        foreach ($user->paper_set($ssel) as $prow) {
132            if (($whyNot = $user->perm_view_paper($prow))) {
133                $errors["#$prow->paperId: " . whyNotText($whyNot, true)] = true;
134                continue;
135            }
136            $rctext = "";
137            if ($this->include_paper)
138                $rctext = GetAbstract_ListAction::render($prow, $user);
139            $last_rc = null;
140            foreach ($prow->viewable_submitted_reviews_and_comments($user) as $rc) {
141                $rctext .= PaperInfo::review_or_comment_text_separator($last_rc, $rc);
142                if (isset($rc->reviewId))
143                    $rctext .= $rf->pretty_text($prow, $rc, $user, false, true);
144                else
145                    $rctext .= $rc->unparse_text($user, true);
146                $last_rc = $rc;
147            }
148            if ($rctext !== "") {
149                if (!$this->include_paper) {
150                    $header = "{$user->conf->short_name} Paper #{$prow->paperId} Reviews and Comments\n";
151                    $rctext = $header . str_repeat("=", 75) . "\n"
152                        . "* Paper #{$prow->paperId} {$prow->title}\n\n" . $rctext;
153                }
154                $texts[$prow->paperId] = $rctext;
155            } else if (($whyNot = $user->perm_review($prow, null, null)))
156                $errors["#$prow->paperId: " . whyNotText($whyNot, true)] = true;
157        }
158        $texts = $ssel->reorder($texts);
159        $first = true;
160        foreach ($texts as &$text) {
161            if (!$first)
162                $text = "\n\n\n" . str_repeat("* ", 37) . "*\n\n\n\n" . $text;
163            $first = false;
164        }
165        unset($text);
166        $user->set_overrides($overrides);
167        $this->finish($user, $texts, $errors);
168    }
169}
170
171class GetScores_ListAction extends ListAction {
172    function allow(Contact $user) {
173        return $user->can_view_some_review();
174    }
175    function run(Contact $user, $qreq, $ssel) {
176        $rf = $user->conf->review_form();
177        $overrides = $user->add_overrides(Contact::OVERRIDE_CONFLICT);
178        // compose scores; NB chair is always forceShow
179        $errors = $texts = $any_scores = array();
180        $any_decision = $any_reviewer_identity = false;
181        foreach ($user->paper_set($ssel) as $row) {
182            if (($whyNot = $user->perm_view_paper($row)))
183                $errors[] = "#$row->paperId: " . whyNotText($whyNot);
184            else if (($whyNot = $user->perm_view_review($row, null, null)))
185                $errors[] = "#$row->paperId: " . whyNotText($whyNot);
186            else {
187                $row->ensure_full_reviews();
188                $a = ["paper" => $row->paperId, "title" => $row->title];
189                if ($row->outcome && $user->can_view_decision($row))
190                    $a["decision"] = $any_decision = $user->conf->decision_name($row->outcome);
191                foreach ($row->viewable_submitted_reviews_by_display($user) as $rrow) {
192                    $view_bound = $user->view_score_bound($row, $rrow);
193                    $this_scores = false;
194                    $b = $a;
195                    foreach ($rf->forder as $field => $f)
196                        if ($f->view_score > $view_bound && $f->has_options
197                            && ($rrow->$field || $f->allow_empty)) {
198                            $b[$f->search_keyword()] = $f->unparse_value($rrow->$field);
199                            $any_scores[$f->search_keyword()] = $this_scores = true;
200                        }
201                    if ($user->can_view_review_identity($row, $rrow)) {
202                        $any_reviewer_identity = true;
203                        $b["reviewername"] = trim($rrow->firstName . " " . $rrow->lastName);
204                        $b["email"] = $rrow->email;
205                    }
206                    if ($this_scores)
207                        arrayappend($texts[$row->paperId], $b);
208                }
209            }
210        }
211        $user->set_overrides($overrides);
212
213        if (!empty($texts)) {
214            $header = ["paper", "title"];
215            if ($any_decision)
216                $header[] = "decision";
217            if ($any_reviewer_identity)
218                array_push($header, "reviewername", "email");
219            return $user->conf->make_csvg("scores")
220                ->select(array_merge($header, array_keys($any_scores)))
221                ->add($ssel->reorder($texts));
222        } else {
223            if (empty($errors))
224                $errors[] = "No papers selected.";
225            Conf::msg_error(join("<br>", $errors));
226        }
227    }
228}
229
230class GetRank_ListAction extends ListAction {
231    function allow(Contact $user) {
232        return $user->conf->setting("tag_rank") && $user->is_reviewer();
233    }
234    function run(Contact $user, $qreq, $ssel) {
235        $settingrank = $user->conf->setting("tag_rank") && $qreq->tag == "~" . $user->conf->setting_data("tag_rank");
236        if (!$user->isPC && !($user->is_reviewer() && $settingrank))
237            return self::EPERM;
238        $tagger = new Tagger($user);
239        if (($tag = $tagger->check($qreq->tag, Tagger::NOVALUE | Tagger::NOCHAIR))) {
240            $real = "";
241            $null = "\n";
242            foreach ($user->paper_set($ssel, ["tagIndex" => $tag, "order" => "order by tagIndex, PaperReview.overAllMerit desc, Paper.paperId"]) as $prow)
243                if ($user->can_change_tag($prow, $tag, null, 1)) {
244                    $csvt = CsvGenerator::quote($prow->title);
245                    if ($prow->tagIndex === null)
246                        $null .= "X,$prow->paperId,$csvt\n";
247                    else if ($real === "" || $lastIndex == $prow->tagIndex - 1)
248                        $real .= ",$prow->paperId,$csvt\n";
249                    else if ($lastIndex == $prow->tagIndex)
250                        $real .= "=,$prow->paperId,$csvt\n";
251                    else
252                        $real .= str_pad("", min($prow->tagIndex - $lastIndex, 5), ">") . ",$prow->paperId,$csvt\n";
253                    $lastIndex = $prow->tagIndex;
254                }
255            $text = "# Edit the rank order by rearranging this file's lines.
256
257# The first line has the highest rank. Lines starting with \"#\" are
258# ignored. Unranked papers appear at the end in lines starting with
259# \"X\", sorted by overall merit. Create a rank by removing the \"X\"s and
260# rearranging the lines. A line starting with \"=\" marks a paper with the
261# same rank as the preceding paper. Lines starting with \">>\", \">>>\",
262# and so forth indicate rank gaps between papers. When you are done,
263# upload the file at\n"
264                . "#   " . hoturl_absolute("offline") . "\n\n"
265                . "Tag: " . trim($qreq->tag) . "\n"
266                . "\n"
267                . $real . $null;
268            downloadText($text, "rank");
269        } else
270            Conf::msg_error($tagger->error_html);
271    }
272}
273
274class GetLead_ListAction extends ListAction {
275    private $type;
276    function __construct($conf, $fj) {
277        $this->type = $fj->type;
278    }
279    function allow(Contact $user) {
280        return $user->isPC;
281    }
282    function run(Contact $user, $qreq, $ssel) {
283        $key = $this->type . "ContactId";
284        $can_view = "can_view_" . $this->type;
285        $texts = array();
286        foreach ($user->paper_set($ssel) as $row)
287            if ($row->$key && $user->$can_view($row, true)) {
288                $name = $user->name_object_for($row->$key);
289                arrayappend($texts[$row->paperId], [$row->paperId, $row->title, $name->firstName, $name->lastName, $name->email]);
290            }
291        return $user->conf->make_csvg($this->type . "s")
292            ->select(["paper", "title", "first", "last", "{$this->type}email"])
293            ->add($ssel->reorder($texts));
294    }
295}
296