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