1<?php 2// profile.php -- HotCRP profile management page 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5require_once("src/initweb.php"); 6 7// check for change-email capabilities 8function change_email_by_capability($Qreq) { 9 global $Conf, $Me; 10 $capmgr = $Conf->capability_manager(); 11 $capdata = $capmgr->check($Qreq->changeemail); 12 if (!$capdata 13 || $capdata->capabilityType != CAPTYPE_CHANGEEMAIL 14 || !($capdata->data = json_decode($capdata->data)) 15 || !get($capdata->data, "uemail")) 16 error_go(false, "That email change code has expired, or you didn’t enter it correctly."); 17 18 if ($capdata->contactId) 19 $Acct = $Conf->user_by_id($capdata->contactId); 20 else 21 error_go(false, "That email change code was created improperly due to a server error. Please create another email change code, or sign out of your current account and create a new account using your preferred email address."); 22 23 if (!$Acct) 24 error_go(false, "No such account."); 25 else if (isset($capdata->data->oldemail) 26 && strcasecmp($Acct->email, $capdata->data->oldemail)) 27 error_go(false, "You have changed your email address since creating that email change code."); 28 29 $email = $capdata->data->uemail; 30 if ($Conf->user_id_by_email($email)) 31 error_go(false, "Email address “" . htmlspecialchars($email) . "” is already in use. You may want to <a href=\"" . hoturl("mergeaccounts") . "\">merge these accounts</a>."); 32 33 $Acct->change_email($email); 34 $capmgr->delete($capdata); 35 36 $Conf->confirmMsg("Your email address has been changed."); 37 if (!$Me->has_database_account() || $Me->contactId == $Acct->contactId) 38 $Me = $Acct->activate($Qreq); 39} 40if ($Qreq->changeemail) 41 change_email_by_capability($Qreq); 42 43if (!$Me->has_email()) 44 $Me->escape(); 45$newProfile = false; 46$useRequest = false; 47$UserStatus = new UserStatus($Me); 48 49if ($Qreq->u === null) { 50 if ($Qreq->user) 51 $Qreq->u = $Qreq->user; 52 else if ($Qreq->contact) 53 $Qreq->u = $Qreq->contact; 54 else if (preg_match(',\A/(?:new|[^\s/]+)\z,i', Navigation::path())) 55 $Qreq->u = substr(Navigation::path(), 1); 56} 57if ($Me->privChair && $Qreq->new) 58 $Qreq->u = "new"; 59 60 61// Load user. 62$Acct = $Me; 63if ($Me->privChair && ($Qreq->u || $Qreq->search)) { 64 if ($Qreq->u === "new") { 65 $Acct = new Contact(null, $Conf); 66 $newProfile = true; 67 } else if (($id = cvtint($Qreq->u)) > 0) 68 $Acct = $Conf->user_by_id($id); 69 else if ($Qreq->u === "" && $Qreq->search) 70 Navigation::redirect_site("users"); 71 else { 72 $Acct = $Conf->user_by_email($Qreq->u); 73 if (!$Acct && $Qreq->search) { 74 $cs = new ContactSearch(ContactSearch::F_USER, $Qreq->u, $Me); 75 if ($cs->ids) { 76 $Acct = $Conf->user_by_id($cs->ids[0]); 77 $list = new SessionList("u/all/" . urlencode($Qreq->search), $cs->ids, "“" . htmlspecialchars($Qreq->u) . "”", hoturl_site_relative_raw("users", ["t" => "all"])); 78 $list->set_cookie(); 79 $Qreq->u = $Acct->email; 80 SelfHref::redirect($Qreq); 81 } 82 } 83 } 84} 85 86// Redirect if requested user isn't loaded user. 87if (!$Acct 88 || ($Qreq->u !== null 89 && $Qreq->u !== (string) $Acct->contactId 90 && strcasecmp($Qreq->u, $Acct->email) 91 && ($Acct->contactId || $Qreq->u !== "new")) 92 || (isset($Qreq->profile_contactid) 93 && $Qreq->profile_contactid !== (string) $Acct->contactId)) { 94 if (!$Acct) 95 Conf::msg_error("Invalid user."); 96 else if (isset($Qreq->save) || isset($Qreq->savebulk)) 97 Conf::msg_error("You’re logged in as a different user now, so your changes were ignored."); 98 unset($Qreq->u, $Qreq->save, $Qreq->savebulk); 99 SelfHref::redirect($Qreq); 100} 101 102$need_highlight = false; 103if (($Acct->contactId != $Me->contactId || !$Me->has_database_account()) 104 && $Acct->has_email() 105 && !$Acct->firstName && !$Acct->lastName && !$Acct->affiliation 106 && !$Qreq->post) { 107 $result = $Conf->qe_raw("select Paper.paperId, authorInformation from Paper join PaperConflict on (PaperConflict.paperId=Paper.paperId and PaperConflict.contactId=$Acct->contactId and PaperConflict.conflictType>=" . CONFLICT_AUTHOR . ")"); 108 while (($prow = PaperInfo::fetch($result, $Me))) 109 foreach ($prow->author_list() as $au) 110 if (strcasecmp($au->email, $Acct->email) == 0 111 && ($au->firstName || $au->lastName || $au->affiliation)) { 112 if (!$Acct->firstName && $au->firstName) { 113 $Acct->firstName = $au->firstName; 114 $need_highlight = true; 115 } 116 if (!$Acct->lastName && $au->lastName) { 117 $Acct->lastName = $au->lastName; 118 $need_highlight = true; 119 } 120 if (!$Acct->affiliation && $au->affiliation) { 121 $Acct->affiliation = $au->affiliation; 122 $need_highlight = true; 123 } 124 } 125} 126 127 128function save_user($cj, $user_status, $Acct, $allow_modification) { 129 global $Conf, $Me, $Now, $newProfile; 130 if ($newProfile) 131 $Acct = null; 132 133 // check for missing fields 134 UserStatus::normalize_name($cj); 135 if ($newProfile && !isset($cj->email)) { 136 $user_status->error_at("email", "Email address required."); 137 return false; 138 } 139 140 // check email 141 if ($newProfile || strcasecmp($cj->email, $Acct->email)) { 142 if ($Acct && $Acct->data("locked")) 143 return $user_status->error_at("email", "This account is locked, so you can’t change its email address."); 144 else if (($new_acct = $Conf->user_by_email($cj->email))) { 145 if ($allow_modification) 146 $cj->id = $new_acct->contactId; 147 else { 148 $msg = "Email address “" . htmlspecialchars($cj->email) . "” is already in use."; 149 if ($Me->privChair) 150 $msg = str_replace("an account", "<a href=\"" . hoturl("profile", "u=" . urlencode($cj->email)) . "\">an account</a>", $msg); 151 if (!$newProfile) 152 $msg .= " You may want to <a href=\"" . hoturl("mergeaccounts") . "\">merge these accounts</a>."; 153 return $user_status->error_at("email", $msg); 154 } 155 } else if ($Conf->external_login()) { 156 if ($cj->email === "") 157 return $user_status->error_at("email", "Not a valid username."); 158 } else if ($cj->email === "") { 159 return $user_status->error_at("email", "You must supply an email address."); 160 } else if (!validate_email($cj->email)) { 161 return $user_status->error_at("email", "“" . htmlspecialchars($cj->email) . "” is not a valid email address."); 162 } else if ($Acct && !$Acct->has_database_account()) { 163 return $user_status->error_at("email", "Your current account is only active on other HotCRP.com sites. Due to a server limitation, you can’t change your email until activating your account on this site."); 164 } 165 if (!$newProfile && !$Me->privChair) { 166 $old_preferredEmail = $Acct->preferredEmail; 167 $Acct->preferredEmail = $cj->email; 168 $capmgr = $Conf->capability_manager(); 169 $rest = array("capability" => $capmgr->create(CAPTYPE_CHANGEEMAIL, array("user" => $Acct, "timeExpires" => $Now + 259200, "data" => json_encode_db(array("oldemail" => $Acct->email, "uemail" => $cj->email))))); 170 $mailer = new HotCRPMailer($Conf, $Acct, null, $rest); 171 $prep = $mailer->make_preparation("@changeemail", $rest); 172 if ($prep->sendable) { 173 $prep->send(); 174 $Conf->warnMsg("Mail has been sent to " . htmlspecialchars($cj->email) . ". Use the link it contains to confirm your email change request."); 175 } else 176 Conf::msg_error("Mail cannot be sent to " . htmlspecialchars($cj->email) . " at this time. Your email address was unchanged."); 177 // Save changes *except* for new email, by restoring old email. 178 $cj->email = $Acct->email; 179 $Acct->preferredEmail = $old_preferredEmail; 180 } 181 } 182 183 // save account 184 return $user_status->save($cj, $Acct); 185} 186 187 188function parseBulkFile($text, $filename) { 189 global $Conf, $Me; 190 $text = cleannl(convert_to_utf8($text)); 191 $filename = $filename ? "$filename:" : "line "; 192 $success = $errors = array(); 193 194 if (!preg_match('/\A[^\r\n]*(?:,|\A)(?:user|email)(?:[,\r\n]|\z)/', $text) 195 && !preg_match('/\A[^\r\n]*,[^\r\n]*,/', $text)) { 196 $tarr = CsvParser::split_lines($text); 197 foreach ($tarr as &$t) { 198 if (($t = trim($t)) && $t[0] !== "#" && $t[0] !== "%") 199 $t = CsvGenerator::quote($t); 200 $t .= "\n"; 201 } 202 unset($t); 203 $text = join("", $tarr); 204 } 205 206 $csv = new CsvParser($text); 207 $csv->set_comment_chars("#%"); 208 if (($line = $csv->next())) { 209 $lcline = array_map(function ($a) { return strtolower(trim($a)); }, $line); 210 if (array_search("email", $lcline) !== false 211 || array_search("user", $lcline) !== false) 212 $csv->set_header($lcline); 213 else if (count($line) == 1) { 214 $csv->set_header(["user"]); 215 $csv->unshift($line); 216 } else { 217 // interpolate a likely header 218 $lcline = []; 219 for ($i = 0; $i < count($line); ++$i) 220 if (validate_email($line[$i]) && array_search("email", $lcline) === false) 221 $lcline[] = "email"; 222 else if (strpos($line[$i], " ") !== false && array_search("name", $lcline) === false) 223 $lcline[] = "name"; 224 else if (array_search($line[$i], ["pc", "chair"]) !== false && array_search("roles", $lcline) === false) 225 $lcline[] = "roles"; 226 else if (array_search("name", $lcline) !== false && array_search("affiliation", $lcline) === false) 227 $lcline[] = "affiliation"; 228 else 229 $lcline[] = "unknown"; 230 $csv->set_header($lcline); 231 $csv->unshift($line); 232 $errors[] = "<span class='lineno'>" . $filename . $csv->lineno() . ":</span> Header missing, assuming “<code>" . join(",", $lcline) . "</code>”"; 233 } 234 235 } 236 237 $saved_users = []; 238 $ustatus = new UserStatus($Me, ["send_email" => true, "no_deprivilege_self" => true]); 239 240 while (($line = $csv->next()) !== false) { 241 $ustatus->set_user(new Contact(null, $Conf)); 242 $ustatus->clear_messages(); 243 $cj = (object) ["id" => null]; 244 $ustatus->parse_csv_group("", $cj, $line); 245 246 if (isset($cj->email) && isset($saved_users[strtolower($cj->email)])) { 247 $errors[] = '<span class="lineno">' . $filename . $csv->lineno() . ":</span> Already saved a user with email “" . htmlspecialchars($cj->email) . "”."; 248 $errors[] = '<span class="lineno">' . $filename . $saved_users[strtolower($cj->email)] . ":</span> (That user was saved here.)"; 249 continue; 250 } 251 252 if (isset($cj->email) && $cj->email !== "") 253 $saved_users[strtolower($cj->email)] = $csv->lineno(); 254 if (($saved_user = save_user($cj, $ustatus, null, true))) 255 $success[] = "<a href=\"" . hoturl("profile", "u=" . urlencode($saved_user->email)) . "\">" 256 . Text::user_html_nolink($saved_user) . "</a>"; 257 foreach ($ustatus->errors() as $e) 258 $errors[] = '<span class="lineno">' . $filename . $csv->lineno() . ":</span> " . $e; 259 } 260 261 if (!empty($ustatus->unknown_topics)) 262 $errors[] = "There were unrecognized topics (" . htmlspecialchars(commajoin(array_keys($ustatus->unknown_topics))) . ")."; 263 if (count($success) == 1) 264 $successMsg = "Saved account " . $success[0] . "."; 265 else if (count($success)) 266 $successMsg = "Saved " . plural($success, "account") . ": " . commajoin($success) . "."; 267 if (count($errors)) 268 $errorMsg = "<div class='parseerr'><p>" . join("</p>\n<p>", $errors) . "</p></div>"; 269 if (count($success) && count($errors)) 270 $Conf->confirmMsg($successMsg . "<br />$errorMsg"); 271 else if (count($success)) 272 $Conf->confirmMsg($successMsg); 273 else if (count($errors)) 274 Conf::msg_error($errorMsg); 275 else 276 $Conf->warnMsg("Nothing to do."); 277 return empty($errors); 278} 279 280if (!$Qreq->post_ok()) 281 /* do nothing */; 282else if ($Qreq->savebulk && $newProfile && $Qreq->has_file("bulk")) { 283 if (($text = $Qreq->file_contents("bulk")) === false) 284 Conf::msg_error("Internal error: cannot read file."); 285 else 286 parseBulkFile($text, $Qreq->file_filename("bulk")); 287 $Qreq->bulkentry = ""; 288 SelfHref::redirect($Qreq, ["anchor" => "bulk"]); 289} else if ($Qreq->savebulk && $newProfile) { 290 $success = true; 291 if ($Qreq->bulkentry && $Qreq->bulkentry !== "Enter users one per line") 292 $success = parseBulkFile($Qreq->bulkentry, ""); 293 if (!$success) 294 $Conf->save_session("profile_bulkentry", array($Now, $Qreq->bulkentry)); 295 SelfHref::redirect($Qreq, ["anchor" => "bulk"]); 296} else if (isset($Qreq->save)) { 297 assert($Acct->is_empty() === $newProfile); 298 $cj = (object) ["id" => $Acct->has_database_account() ? $Acct->contactId : "new"]; 299 $UserStatus->set_user($Acct); 300 $UserStatus->parse_request_group("", $cj, $Qreq); 301 if ($newProfile) 302 $UserStatus->send_email = true; 303 $saved_user = save_user($cj, $UserStatus, $Acct, false); 304 if (!$UserStatus->has_error()) { 305 if ($UserStatus->has_messages()) 306 $Conf->msg($UserStatus->problem_status(), $UserStatus->messages()); 307 if ($newProfile) { 308 $Conf->msg("xconfirm", "Created an account for <a href=\"" . hoturl("profile", "u=" . urlencode($saved_user->email)) . "\">" . Text::user_html_nolink($saved_user) . "</a>. A password has been emailed to that address. You may now create another account."); 309 } else { 310 $Conf->msg("xconfirm", "Profile updated."); 311 if ($Acct->contactId != $Me->contactId) 312 $Qreq->u = $Acct->email; 313 } 314 if (isset($Qreq->redirect)) 315 go(hoturl("index")); 316 else { 317 $xcj = []; 318 if ($newProfile) { 319 foreach (["roles", "follow", "tags"] as $k) 320 if (isset($cj->$k)) 321 $xcj[$k] = $cj->$k; 322 } 323 if ($UserStatus->has_warning()) 324 $xcj["warning_fields"] = $UserStatus->problem_fields(); 325 $Conf->save_session("profile_redirect", $xcj); 326 SelfHref::redirect($Qreq); 327 } 328 } 329} else if (isset($Qreq->merge) && !$newProfile 330 && $Acct->contactId == $Me->contactId) 331 go(hoturl("mergeaccounts")); 332 333function databaseTracks($who) { 334 global $Conf; 335 $tracks = (object) array("soleAuthor" => array(), 336 "author" => array(), 337 "review" => array(), 338 "comment" => array()); 339 340 // find authored papers 341 $result = $Conf->qe_raw("select Paper.paperId, count(pc.contactId) 342 from Paper 343 join PaperConflict c on (c.paperId=Paper.paperId and c.contactId=$who and c.conflictType>=" . CONFLICT_AUTHOR . ") 344 join PaperConflict pc on (pc.paperId=Paper.paperId and pc.conflictType>=" . CONFLICT_AUTHOR . ") 345 group by Paper.paperId order by Paper.paperId"); 346 while (($row = edb_row($result))) { 347 if ($row[1] == 1) 348 $tracks->soleAuthor[] = $row[0]; 349 $tracks->author[] = $row[0]; 350 } 351 352 // find reviews 353 $result = $Conf->qe_raw("select paperId from PaperReview 354 where PaperReview.contactId=$who 355 group by paperId order by paperId"); 356 while (($row = edb_row($result))) 357 $tracks->review[] = $row[0]; 358 359 // find comments 360 $result = $Conf->qe_raw("select paperId from PaperComment 361 where PaperComment.contactId=$who 362 group by paperId order by paperId"); 363 while (($row = edb_row($result))) 364 $tracks->comment[] = $row[0]; 365 366 return $tracks; 367} 368 369function textArrayPapers($pids) { 370 return commajoin(preg_replace('/(\d+)/', "<a href='" . hoturl("paper", "p=\$1&ls=" . join("+", $pids)) . "'>\$1</a>", $pids)); 371} 372 373if (isset($Qreq->delete) && !Dbl::has_error() && $Qreq->post_ok()) { 374 if (!$Me->privChair) 375 Conf::msg_error("Only administrators can delete accounts."); 376 else if ($Acct->contactId == $Me->contactId) 377 Conf::msg_error("You aren’t allowed to delete your own account."); 378 else if ($Acct->has_database_account()) { 379 $tracks = databaseTracks($Acct->contactId); 380 if (!empty($tracks->soleAuthor)) 381 Conf::msg_error("This account can’t be deleted since it is sole contact for " . pluralx($tracks->soleAuthor, "paper") . " " . textArrayPapers($tracks->soleAuthor) . ". You will be able to delete the account after deleting those papers or adding additional paper contacts."); 382 else if ($Acct->data("locked")) 383 Conf::msg_error("This account is locked and can’t be deleted."); 384 else { 385 $Conf->q("insert into DeletedContactInfo set contactId=?, firstName=?, lastName=?, unaccentedName=?, email=?", $Acct->contactId, $Acct->firstName, $Acct->lastName, $Acct->unaccentedName, $Acct->email); 386 foreach (array("ContactInfo", 387 "PaperComment", "PaperConflict", "PaperReview", 388 "PaperReviewPreference", "PaperReviewRefused", 389 "PaperWatch", "ReviewRating", "TopicInterest") 390 as $table) 391 $Conf->qe_raw("delete from $table where contactId=$Acct->contactId"); 392 // delete twiddle tags 393 $assigner = new AssignmentSet($Me, true); 394 $assigner->parse("paper,tag\nall,{$Acct->contactId}~all#clear\n"); 395 $assigner->execute(); 396 // clear caches 397 if ($Acct->isPC || $Acct->privChair) 398 $Conf->invalidate_caches(["pc" => 1]); 399 // done 400 $Conf->confirmMsg("Permanently deleted account " . htmlspecialchars($Acct->email) . "."); 401 $Me->log_activity_for($Acct, "Permanently deleted account " . htmlspecialchars($Acct->email)); 402 go(hoturl("users", "t=all")); 403 } 404 } 405} 406 407function echo_modes($hlbulk) { 408 global $Me, $Acct, $newProfile; 409 echo '<div class="psmode">', 410 '<div class="', ($hlbulk == 0 ? "papmodex" : "papmode"), '">', 411 Ht::link($newProfile || $Me->email == $Acct->email ? "Your profile" : "Profile", selfHref(["u" => null])), 412 '</div><div class="', ($hlbulk == 1 ? "papmodex" : "papmode"), '">'; 413 if ($newProfile) 414 echo Ht::link("New account", "", ["class" => "ui tla has-focus-history", "data-fold-target" => "9c"]); 415 else 416 echo Ht::link("New account", hoturl("profile", "u=new")); 417 echo '</div><div class="', ($hlbulk == 2 ? "papmodex" : "papmode"), '">'; 418 if ($newProfile) 419 echo Ht::link("Bulk update", "#bulk", ["class" => "ui tla has-focus-history", "data-fold-target" => "9o"]); 420 else 421 echo Ht::link("Bulk update", hoturl("profile", "u=new#bulk")); 422 echo '</div></div><hr class="c" style="margin-bottom:24px" />', "\n"; 423} 424 425 426// set session list 427if (!$newProfile 428 && isset($_COOKIE["hotlist-info"]) 429 && ($list = SessionList::decode_info_string($_COOKIE["hotlist-info"])) 430 && $list->list_type() === "u" 431 && $list->set_current_id($Acct->contactId)) { 432 $Conf->set_active_list($list); 433} 434 435if ($newProfile) { 436 $title = "User update"; 437} else if (strcasecmp($Me->email, $Acct->email) == 0) { 438 $title = "Profile"; 439} else { 440 $title = $Me->name_html_for($Acct) . " profile"; 441} 442$Conf->header($title, "account", ["action_bar" => actionBar("account")]); 443 444$useRequest = !$Acct->has_database_account() && isset($Qreq->watchreview); 445if ($UserStatus->has_error()) 446 $need_highlight = $useRequest = true; 447 448if (!$UserStatus->has_error() && $Conf->session("freshlogin") === "redirect") 449 $Conf->save_session("freshlogin", null); 450// Set warnings 451if (!$newProfile) { 452 if (!$Acct->firstName && !$Acct->lastName) { 453 $UserStatus->warning_at("firstName", "Please enter your name."); 454 $UserStatus->warning_at("lastName", false); 455 } 456 if (!$Acct->affiliation) 457 $UserStatus->warning_at("affiliation", "Please enter your affiliation (use “None” or “Unaffiliated” if you have none)."); 458 if ($Acct->is_pc_member()) { 459 if (!$Acct->collaborators) 460 $UserStatus->warning_at("collaborators", "Please enter your recent collaborators and other affiliations. This information can help detect conflicts of interest. Enter “None” if you have none."); 461 if ($Conf->topic_map() && !$Acct->topic_interest_map()) 462 $UserStatus->warning_at("topics", "Please enter your topic interests. We use topic interests to improve the paper assignment process."); 463 } 464} 465 466 467$UserStatus->set_user($Acct); 468$userj = $UserStatus->user_json(["include_password" => true]); 469if (!$useRequest && $Me->privChair && $Acct->is_empty() 470 && ($Qreq->role === "chair" || $Qreq->role === "pc")) { 471 $userj->roles = (object) [$Qreq->role => true]; 472} 473 474if ($useRequest) { 475 $UserStatus->ignore_msgs = true; 476 $formcj = (object) ["id" => $Acct->has_database_account() ? $Acct->contactId : "new"]; 477 $UserStatus->parse_request_group("", $formcj, $Qreq); 478} else { 479 $formcj = $userj; 480} 481if (($prdj = $Conf->session("profile_redirect"))) { 482 $Conf->save_session("profile_redirect", null); 483 foreach ($prdj as $k => $v) { 484 if ($k === "warning_fields") { 485 foreach ($v as $k) 486 $UserStatus->warning_at($k, null); 487 } else { 488 $formcj->$k = $v; 489 } 490 } 491} 492 493$form_params = array(); 494if ($newProfile) 495 $form_params[] = "u=new"; 496else if ($Me->contactId != $Acct->contactId) 497 $form_params[] = "u=" . urlencode($Acct->email); 498if (isset($Qreq->ls)) 499 $form_params[] = "ls=" . urlencode($Qreq->ls); 500if ($newProfile) 501 echo '<div id="foldbulk" class="fold9' . ($Qreq->savebulk ? "o" : "c") . ' js-fold-focus"><div class="fn9">'; 502 503echo Ht::form(hoturl_post("profile", join("&", $form_params)), 504 array("id" => "profile-form")), 505 // Don't want chrome to autofill the password changer. 506 // But chrome defaults to autofilling the password changer 507 // unless we supply an earlier password input. 508 Ht::password("chromefooler", "", ["class" => "ignore-diff hidden"]), 509 Ht::hidden("profile_contactid", $Acct->contactId); 510if (isset($Qreq->redirect)) 511 echo Ht::hidden("redirect", $Qreq->redirect); 512if ($Me->privChair) 513 echo Ht::hidden("whichpassword", ""); 514 515if ($newProfile) 516 echo_modes(1); 517else if ($Me->privChair) 518 echo_modes(0); 519 520if ($UserStatus->has_messages()) { 521 $status = 0; 522 $msgs = []; 523 foreach ($UserStatus->messages(true) as $m) { 524 $status = max($m[2], $status); 525 $msgs[] = '<div class="mmm">' . $m[1] . '</div>'; 526 } 527 echo '<div class="msgs-wide">', Ht::xmsg($status, join("", $msgs)), "</div>\n"; 528} 529 530echo '<div id="foldaccount" class="'; 531if (isset($formcj->roles) 532 && (isset($formcj->roles->pc) || isset($formcj->roles->chair))) 533 echo "fold1o fold2o"; 534else if (isset($formcj->roles) && isset($formcj->roles->sysadmin)) 535 echo "fold1c fold2o"; 536else 537 echo "fold1c fold2c"; 538echo "\">\n"; 539 540 541$UserStatus->set_user($Acct); 542$UserStatus->render_group("", $userj, $formcj); 543 544if ($UserStatus->global_user() && false) { 545 echo '<div class="profile-g"><div class="checki"><label><span class="checkc">', 546 Ht::checkbox("saveglobal", 1, $useRequest ? !!$Qreq->saveglobal : true, ["class" => "ignore-diff"]), 547 '</span>Update global profile</label></div></div>'; 548} 549 550$buttons = [Ht::submit("save", $newProfile ? "Create account" : "Save changes", ["class" => "btn btn-primary"]), 551 Ht::submit("cancel", "Cancel", ["class" => "btn"])]; 552 553if ($Me->privChair && !$newProfile && $Me->contactId != $Acct->contactId) { 554 $tracks = databaseTracks($Acct->contactId); 555 $args = ["class" => "btn ui"]; 556 if (!empty($tracks->soleAuthor)) { 557 $args["class"] .= " js-cannot-delete-user"; 558 $args["data-sole-author"] = pluralx($tracks->soleAuthor, "paper") . " " . textArrayPapers($tracks->soleAuthor); 559 } else { 560 $args["class"] .= " js-delete-user"; 561 $x = $y = array(); 562 if (!empty($tracks->author)) { 563 $x[] = "contact for " . pluralx($tracks->author, "paper") . " " . textArrayPapers($tracks->author); 564 $y[] = "delete " . pluralx($tracks->author, "this") . " " . pluralx($tracks->author, "authorship association"); 565 } 566 if (!empty($tracks->review)) { 567 $x[] = "reviewer for " . pluralx($tracks->review, "paper") . " " . textArrayPapers($tracks->review); 568 $y[] = "<strong>permanently delete</strong> " . pluralx($tracks->review, "this") . " " . pluralx($tracks->review, "review"); 569 } 570 if (!empty($tracks->comment)) { 571 $x[] = "commenter for " . pluralx($tracks->comment, "paper") . " " . textArrayPapers($tracks->comment); 572 $y[] = "<strong>permanently delete</strong> " . pluralx($tracks->comment, "this") . " " . pluralx($tracks->comment, "comment"); 573 } 574 if (!empty($x)) { 575 $args["data-delete-info"] = "<p>This user is " . commajoin($x) . ". Deleting the user will also " . commajoin($y) . ".</p>"; 576 } 577 } 578 $buttons[] = ""; 579 $buttons[] = [Ht::button("Delete user", $args), "(admin only)"]; 580} 581if (!$newProfile && $Acct->contactId == $Me->contactId) 582 array_push($buttons, "", Ht::submit("merge", "Merge with another account")); 583 584echo Ht::actions($buttons, ["class" => "aab aabr aabig"]); 585 586echo "</div>\n", // foldaccount 587 "</form>\n"; 588 589if ($newProfile) { 590 echo '</div><div class="fx9">'; 591 echo Ht::form(hoturl_post("profile", join("&", $form_params))), 592 "<div class='profiletext", ($UserStatus->has_error() ? " alert" : ""), "'>\n", 593 // Don't want chrome to autofill the password changer. 594 // But chrome defaults to autofilling the password changer 595 // unless we supply an earlier password input. 596 Ht::password("chromefooler", "", ["class" => "ignore-diff hidden"]); 597 echo_modes(2); 598 599 $bulkentry = $Qreq->bulkentry; 600 if ($bulkentry === null 601 && ($session_bulkentry = $Conf->session("profile_bulkentry")) 602 && is_array($session_bulkentry) && $session_bulkentry[0] > $Now - 5) { 603 $bulkentry = $session_bulkentry[1]; 604 $Conf->save_session("profile_bulkentry", null); 605 } 606 echo '<div class="f-i">', 607 Ht::textarea("bulkentry", $bulkentry, 608 ["rows" => 1, "cols" => 80, "placeholder" => "Enter users one per line", "class" => "want-focus need-autogrow"]), 609 '</div>'; 610 611 echo '<div class="g"><strong>OR</strong> ', 612 '<input type="file" name="bulk" size="30" /></div>'; 613 614 echo '<div>', Ht::submit("savebulk", "Save accounts", ["class" => "btn btn-primary"]), '</div>'; 615 616 echo "<p>Enter or upload CSV user data with header. For example:</p>\n", 617 '<pre class="entryexample"> 618name,email,affiliation,roles 619John Adams,john@earbox.org,UC Berkeley,pc 620"Adams, John Quincy",quincy@whitehouse.gov 621</pre>', "\n", 622 '<p>Or just enter an email address per line.</p>', 623 '<p>Supported CSV fields include:</p><table>', 624 '<tr><td class="lmcaption"><code>name</code></td>', 625 '<td>User name</td></tr>', 626 '<tr><td class="lmcaption"><code>first</code></td>', 627 '<td>First name</td></tr>', 628 '<tr><td class="lmcaption"><code>last</code></td>', 629 '<td>Last name</td></tr>', 630 '<tr><td class="lmcaption"><code>affiliation</code></td>', 631 '<td>Affiliation</td></tr>', 632 '<tr><td class="lmcaption"><code>roles</code></td>', 633 '<td>User roles: blank, “<code>pc</code>”, “<code>chair</code>”, or “<code>sysadmin</code>”</td></tr>', 634 '<tr><td class="lmcaption"><code>tags</code></td>', 635 '<td>PC tags (space-separated)</td></tr>', 636 '<tr><td class="lmcaption"><code>add_tags</code>, <code>remove_tags</code></td>', 637 '<td>PC tags to add or remove</td></tr>', 638 '<tr><td class="lmcaption"><code>collaborators</code></td>', 639 '<td>Collaborators</td></tr>', 640 '<tr><td class="lmcaption"><code>follow</code></td>', 641 '<td>Email notification: blank, “<code>reviews</code>”, “<code>allreviews</code>”</td></tr>', 642 "</table>\n"; 643 644 echo '</div></form></div></div>'; 645} 646 647 648Ht::stash_script('focus_within($("#profile-form"))'); 649if ($newProfile) 650 Ht::stash_script("focus_fold.hash(true)"); 651else 652 Ht::stash_script('hiliter_children("#profile-form",true)'); 653$Conf->footer(); 654