1<?php 2// users.php -- HotCRP people listing/editing page 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5require_once("src/initweb.php"); 6require_once("src/contactlist.php"); 7 8$getaction = ""; 9if (isset($Qreq->get)) 10 $getaction = $Qreq->get; 11else if (isset($Qreq->getgo) && isset($Qreq->getaction)) 12 $getaction = $Qreq->getaction; 13 14 15// list type 16$tOpt = array(); 17if ($Me->can_view_pc()) 18 $tOpt["pc"] = "Program committee"; 19if ($Me->can_view_contact_tags() && count($pctags = $Conf->pc_tags())) { 20 foreach ($pctags as $t) 21 if ($t != "pc") 22 $tOpt["#$t"] = "#$t program committee"; 23} 24if ($Me->isPC) 25 $tOpt["admin"] = "System administrators"; 26if ($Me->privChair || ($Me->isPC && $Conf->setting("pc_seeallrev"))) { 27 $tOpt["re"] = "All reviewers"; 28 $tOpt["ext"] = "External reviewers"; 29 $tOpt["extsub"] = "External reviewers who completed a review"; 30} 31if ($Me->isPC) 32 $tOpt["req"] = "External reviewers you requested"; 33if ($Me->privChair || ($Me->isPC && $Conf->subBlindNever())) 34 $tOpt["au"] = "Contact authors of submitted papers"; 35if ($Me->privChair 36 || ($Me->isPC && $Conf->timePCViewDecision(true))) 37 $tOpt["auacc"] = "Contact authors of accepted papers"; 38if ($Me->privChair 39 || ($Me->isPC && $Conf->subBlindNever() && $Conf->timePCViewDecision(true))) 40 $tOpt["aurej"] = "Contact authors of rejected papers"; 41if ($Me->privChair) { 42 $tOpt["auuns"] = "Contact authors of non-submitted papers"; 43 $tOpt["all"] = "All users"; 44} 45if (empty($tOpt)) 46 $Me->escape(); 47if (isset($Qreq->t) && !isset($tOpt[$Qreq->t])) { 48 if (str_starts_with($Qreq->t, "pc:") 49 && isset($tOpt["#" . substr($Qreq->t, 3)])) 50 $Qreq->t = "#" . substr($Qreq->t, 3); 51 else if (isset($tOpt["#" . $Qreq->t])) 52 $Qreq->t = "#" . $Qreq->t; 53 else if ($Qreq->t == "#pc") 54 $Qreq->t = "pc"; 55 else { 56 Conf::msg_error("Unknown user collection."); 57 unset($Qreq->t); 58 } 59} 60if (!isset($Qreq->t)) 61 $Qreq->t = key($tOpt); 62 63 64// paper selection and download actions 65function paperselPredicate($papersel) { 66 return "ContactInfo.contactId" . sql_in_numeric_set($papersel); 67} 68 69$Qreq->allow_a("pap"); 70if (isset($Qreq->pap) && is_string($Qreq->pap)) 71 $Qreq->pap = preg_split('/\s+/', $Qreq->pap); 72if ((isset($Qreq->pap) && is_array($Qreq->pap)) 73 || ($getaction && !isset($Qreq->pap))) { 74 $allowed_papers = array(); 75 $pl = new ContactList($Me, true); 76 // Ensure that we only select contacts we're allowed to see. 77 if (($rows = $pl->rows($Qreq->t))) { 78 foreach ($rows as $row) 79 $allowed_papers[$row->contactId] = true; 80 } 81 $papersel = array(); 82 if (isset($Qreq->pap)) { 83 foreach ($Qreq->pap as $p) 84 if (($p = cvtint($p)) > 0 && isset($allowed_papers[$p])) 85 $papersel[] = $p; 86 } else 87 $papersel = array_keys($allowed_papers); 88 if (empty($papersel)) 89 unset($papersel); 90} 91 92if ($getaction == "nameemail" && isset($papersel) && $Me->isPC) { 93 $result = $Conf->qe_raw("select firstName first, lastName last, email, affiliation from ContactInfo where " . paperselPredicate($papersel) . " order by lastName, firstName, email"); 94 $people = edb_orows($result); 95 csv_exit($Conf->make_csvg("users") 96 ->select(["first", "last", "email", "affiliation"]) 97 ->add($people)); 98} 99 100function urlencode_matches($m) { 101 return urlencode($m[0]); 102} 103 104if ($getaction == "pcinfo" && isset($papersel) && $Me->privChair) { 105 $users = []; 106 $result = $Conf->qe_raw("select ContactInfo.* from ContactInfo where " . paperselPredicate($papersel)); 107 while (($user = Contact::fetch($result, $Conf))) 108 $users[] = $user; 109 Dbl::free($result); 110 111 usort($users, "Contact::compare"); 112 Contact::load_topic_interests($users); 113 114 // NB This format is expected to be parsed by profile.php's bulk upload. 115 $tagger = new Tagger($Me); 116 $people = []; 117 $has = (object) []; 118 foreach ($users as $user) { 119 $row = (object) ["first" => $user->firstName, "last" => $user->lastName, 120 "email" => $user->email, "phone" => $user->phone, 121 "disabled" => !!$user->disabled, "affiliation" => $user->affiliation, 122 "collaborators" => rtrim($user->collaborators)]; 123 if ($user->preferredEmail && $user->preferredEmail !== $user->email) 124 $row->preferred_email = $user->preferredEmail; 125 if ($user->contactTags) 126 $row->tags = $tagger->unparse($user->contactTags); 127 foreach ($user->topic_interest_map() as $t => $i) { 128 $k = "topic$t"; 129 $row->$k = $i; 130 } 131 $f = array(); 132 if ($user->defaultWatch & Contact::WATCH_REVIEW) 133 $f[] = "reviews"; 134 if (($user->defaultWatch & Contact::WATCH_REVIEW_ALL) 135 && ($user->roles & Contact::ROLE_PCLIKE)) 136 $f[] = "allreviews"; 137 if ($user->defaultWatch & Contact::WATCH_REVIEW_MANAGED) 138 $f[] = "managedreviews"; 139 if ($user->defaultWatch & Contact::WATCH_FINAL_SUBMIT_ALL) 140 $f[] = "allfinal"; 141 $row->follow = join(",", $f); 142 if ($user->roles & (Contact::ROLE_PC | Contact::ROLE_ADMIN | Contact::ROLE_CHAIR)) { 143 $r = array(); 144 if ($user->roles & Contact::ROLE_CHAIR) 145 $r[] = "chair"; 146 if ($user->roles & Contact::ROLE_PC) 147 $r[] = "pc"; 148 if ($user->roles & Contact::ROLE_ADMIN) 149 $r[] = "sysadmin"; 150 $row->roles = join(",", $r); 151 } else 152 $row->roles = ""; 153 $people[] = $row; 154 155 foreach ((array) $row as $k => $v) 156 if ($v !== null && $v !== false && $v !== "") 157 $has->$k = true; 158 } 159 160 $header = array("first", "last", "email"); 161 if (isset($has->preferred_email)) 162 $header[] = "preferred_email"; 163 $header[] = "roles"; 164 if (isset($has->tags)) 165 $header[] = "tags"; 166 array_push($header, "affiliation", "collaborators", "follow"); 167 if (isset($has->phone)) 168 $header[] = "phone"; 169 $selection = $header; 170 foreach ($Conf->topic_map() as $t => $tn) { 171 $k = "topic$t"; 172 if (isset($has->$k)) { 173 $header[] = "topic: " . $tn; 174 $selection[] = $k; 175 } 176 } 177 csv_exit($Conf->make_csvg("pcinfo")->select($selection, $header)->add($people)); 178} 179 180 181// modifications 182function modify_confirm($j, $ok_message, $ok_message_optional) { 183 global $Conf; 184 if (get($j, "ok") && get($j, "warnings")) 185 $Conf->warnMsg("<div>" . join("</div><div style='margin-top:0.5em'>", $j->warnings) . "</div>"); 186 if (get($j, "ok") && $ok_message 187 && (!$ok_message_optional || !get($j, "warnings")) 188 && (!isset($j->users) || !empty($j->users))) 189 $Conf->confirmMsg($ok_message); 190} 191 192if ($Me->privChair && $Qreq->modifygo && $Qreq->post_ok() && isset($papersel)) { 193 if ($Qreq->modifytype == "disableaccount") 194 modify_confirm(UserActions::disable($Me, $papersel), "Accounts disabled.", true); 195 else if ($Qreq->modifytype == "enableaccount") 196 modify_confirm(UserActions::enable($Me, $papersel), "Accounts enabled.", true); 197 else if ($Qreq->modifytype == "resetpassword" 198 && $Me->can_change_password(null)) 199 modify_confirm(UserActions::reset_password($Me, $papersel), "Passwords reset. <a href=\"" . hoturl_post("users", "t=" . urlencode($Qreq->t) . "&modifygo=1&modifytype=sendaccount&pap=" . join("+", $papersel)) . "\">Send account information to those accounts</a>", false); 200 else if ($Qreq->modifytype == "sendaccount") 201 modify_confirm(UserActions::send_account_info($Me, $papersel), "Account information sent.", false); 202 unset($Qreq->modifygo, $Qreq->modifytype); 203 SelfHref::redirect($Qreq); 204} 205 206function do_tags($qreq) { 207 global $Conf, $Me, $papersel; 208 // check tags 209 $tagger = new Tagger($Me); 210 $t1 = array(); 211 $errors = array(); 212 foreach (preg_split('/[\s,]+/', (string) $qreq->tag) as $t) { 213 if ($t === "") 214 /* nada */; 215 else if (!($t = $tagger->check($t, Tagger::NOPRIVATE))) 216 $errors[] = $tagger->error_html; 217 else if (TagInfo::base($t) === "pc") 218 $errors[] = "The “pc” user tag is set automatically for all PC members."; 219 else 220 $t1[] = $t; 221 } 222 if (count($errors)) 223 return Conf::msg_error(join("<br>", $errors)); 224 else if (!count($t1)) 225 return $Conf->warnMsg("Nothing to do."); 226 227 // modify database 228 Dbl::qe("lock tables ContactInfo write"); 229 Conf::$no_invalidate_caches = true; 230 $users = array(); 231 if ($qreq->tagtype === "s") { 232 // erase existing tags 233 $likes = array(); 234 $removes = array(); 235 foreach ($t1 as $t) { 236 list($tag, $index) = TagInfo::unpack($t); 237 $removes[] = $t; 238 $likes[] = "contactTags like " . Dbl::utf8ci("'% " . sqlq_for_like($tag) . "#%'"); 239 } 240 foreach (Dbl::fetch_first_columns(Dbl::qe("select contactId from ContactInfo where " . join(" or ", $likes))) as $cid) 241 $users[(int) $cid] = (object) array("id" => (int) $cid, "add_tags" => [], "remove_tags" => $removes); 242 } 243 // account for request 244 $key = $qreq->tagtype === "d" ? "remove_tags" : "add_tags"; 245 foreach ($papersel as $cid) { 246 if (!isset($users[(int) $cid])) 247 $users[(int) $cid] = (object) array("id" => (int) $cid, "add_tags" => [], "remove_tags" => []); 248 $users[(int) $cid]->$key = array_merge($users[(int) $cid]->$key, $t1); 249 } 250 // apply modifications 251 $us = new UserStatus($Me, ["send_email" => false]); 252 foreach ($users as $cid => $cj) { 253 $us->save($cj); 254 } 255 Dbl::qe("unlock tables"); 256 Conf::$no_invalidate_caches = false; 257 $Conf->invalidate_caches(["pc" => true]); 258 // report 259 if (!$us->has_error()) { 260 $Conf->confirmMsg("Tags saved."); 261 unset($qreq->tagact, $qreq->tag); 262 SelfHref::redirect($qreq); 263 } else 264 Conf::msg_error($us->errors()); 265} 266 267if ($Me->privChair && $Qreq->tagact && $Qreq->post_ok() && isset($papersel) 268 && preg_match('/\A[ads]\z/', (string) $Qreq->tagtype)) 269 do_tags($Qreq); 270 271 272// set scores to view 273if (isset($Qreq->redisplay)) { 274 $Conf->save_session("uldisplay", ""); 275 foreach (ContactList::$folds as $key) 276 displayOptionsSet("uldisplay", $key, $Qreq->get("show$key", 0)); 277 foreach ($Conf->all_review_fields() as $f) 278 if ($f->has_options) 279 displayOptionsSet("uldisplay", $f->id, $Qreq->get("show{$f->id}", 0)); 280} 281if (isset($Qreq->scoresort)) 282 $Qreq->scoresort = ListSorter::canonical_short_score_sort($Qreq->scoresort); 283if (isset($Qreq->scoresort)) 284 $Conf->save_session("scoresort", $Qreq->scoresort); 285 286 287if ($Qreq->t === "pc") 288 $title = "Program committee"; 289else if (str_starts_with($Qreq->t, "#")) 290 $title = "#" . substr($Qreq->t, 1) . " program committee"; 291else 292 $title = "Users"; 293$Conf->header($title, "accounts", ["action_bar" => actionBar("account")]); 294 295 296$pl = new ContactList($Me, true, $Qreq); 297$pl_text = $pl->table_html($Qreq->t, hoturl("users", ["t" => $Qreq->t]), 298 $tOpt[$Qreq->t], 'uldisplay.'); 299 300 301// form 302echo "<div class='g'></div>\n"; 303if (count($tOpt) > 1) { 304 echo "<table id='contactsform' class='tablinks1'> 305<tr><td><div class='tlx'><div class='tld1'>"; 306 307 echo Ht::form(hoturl("users"), ["method" => "get"]); 308 if (isset($Qreq->sort)) 309 echo Ht::hidden("sort", $Qreq->sort); 310 echo Ht::select("t", $tOpt, $Qreq->t, ["class" => "want-focus"]), 311 " ", Ht::submit("Go"), "</form>"; 312 313 echo "</div><div class='tld2'>"; 314 315 // Display options 316 echo Ht::form(hoturl("users"), ["method" => "get"]); 317 foreach (array("t", "sort") as $x) 318 if (isset($Qreq[$x])) 319 echo Ht::hidden($x, $Qreq[$x]); 320 321 echo "<table><tr><td><strong>Show:</strong> </td> 322 <td class='pad'>"; 323 foreach (array("tags" => "Tags", 324 "aff" => "Affiliations", "collab" => "Collaborators", 325 "topics" => "Topics") as $fold => $text) 326 if (get($pl->have_folds, $fold) !== null) { 327 $k = array_search($fold, ContactList::$folds) + 1; 328 echo Ht::checkbox("show$fold", 1, $pl->have_folds[$fold], 329 ["data-fold-target" => "foldul#$k", "class" => "uich js-foldup"]), 330 " ", Ht::label($text), "<br />\n"; 331 } 332 echo "</td>"; 333 if (isset($pl->scoreMax)) { 334 echo "<td class='pad'>"; 335 $revViewScore = $Me->permissive_view_score_bound(); 336 foreach ($Conf->all_review_fields() as $f) 337 if ($f->view_score > $revViewScore && $f->has_options) { 338 $checked = strpos(displayOptionsSet("uldisplay"), $f->id) !== false; 339 echo Ht::checkbox("show{$f->id}", 1, $checked), 340 " ", Ht::label($f->name_html), "<br />"; 341 } 342 echo "</td>"; 343 } 344 echo "<td>", Ht::submit("redisplay", "Redisplay"), "</td></tr>\n"; 345 if (isset($pl->scoreMax)) { 346 $ss = []; 347 foreach (ListSorter::score_sort_selector_options() as $k => $v) 348 if (in_array($k, ["average", "variance", "maxmin"])) 349 $ss[$k] = $v; 350 echo "<tr><td colspan='3'><div class='g'></div><b>Sort scores by:</b> ", 351 Ht::select("scoresort", $ss, 352 ListSorter::canonical_long_score_sort($Conf->session("scoresort", "A"))), 353 "</td></tr>"; 354 } 355 echo "</table></form>"; 356 357 echo "</div></div></td></tr>\n"; 358 359 // Tab selectors 360 echo "<tr><td class='tllx'><table><tr> 361 <td><div class='tll1'><a class='ui tla' href=''>User selection</a></div></td> 362 <td><div class='tll2'><a class='ui tla' href=''>Display options</a></div></td> 363</tr></table></td></tr> 364</table>\n\n"; 365} 366 367 368if ($Me->privChair && $Qreq->t == "pc") 369 $Conf->infoMsg("<p><a href='" . hoturl("profile", "u=new&role=pc") . "' class='btn'>Create accounts</a></p><p>Select a PC member’s name to edit their profile or remove them from the PC.</p>"); 370else if ($Me->privChair && $Qreq->t == "all") 371 $Conf->infoMsg("<p><a href='" . hoturl("profile", "u=new") . "' class='btn'>Create accounts</a></p><p>Select a user to edit their profile. Select " . Ht::img("viewas.png", "[Act as]") . " to view the site as that user would see it.</p>"); 372 373 374if ($pl->any->sel) { 375 echo Ht::form(hoturl_post("users", ["t" => $Qreq->t])), "<div>"; 376 if (isset($Qreq->sort)) 377 echo Ht::hidden("sort", $Qreq->sort); 378} 379echo $pl_text; 380if ($pl->any->sel) 381 echo "</div></form>"; 382 383 384$Conf->footer(); 385