1<?php 2// contact.php -- HotCRP helper class representing system users 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5class Contact_Update { 6 public $qv = []; 7 public $cdb_qf = []; 8 public $changing_email; 9 function __construct($changing_email) { 10 $this->changing_email = $changing_email; 11 } 12} 13 14class Contact { 15 static public $rights_version = 1; 16 static public $trueuser_privChair = null; 17 static public $allow_nonexistent_properties = false; 18 19 public $contactId = 0; 20 public $contactDbId = 0; 21 public $conf; 22 public $confid; 23 24 public $firstName = ""; 25 public $lastName = ""; 26 public $unaccentedName = ""; 27 public $nameAmbiguous; 28 public $email = ""; 29 public $preferredEmail = ""; 30 public $sorter = ""; 31 public $sort_position; 32 33 public $affiliation = ""; 34 public $country; 35 public $collaborators; 36 public $phone; 37 public $birthday; 38 public $gender; 39 40 private $password = ""; 41 private $passwordTime = 0; 42 private $passwordUseTime = 0; 43 private $_contactdb_user = false; 44 45 public $disabled = false; 46 public $activity_at = false; 47 private $lastLogin; 48 public $creationTime = 0; 49 private $updateTime = 0; 50 private $data = null; 51 private $_topic_interest_map; 52 private $_name_for_map = []; 53 private $_contact_sorter_map = []; 54 const WATCH_REVIEW_EXPLICIT = 1; // only in PaperWatch 55 const WATCH_REVIEW = 2; 56 const WATCH_REVIEW_ALL = 4; 57 const WATCH_REVIEW_MANAGED = 8; 58 const WATCH_FINAL_SUBMIT_ALL = 32; 59 public $defaultWatch = self::WATCH_REVIEW; 60 61 // Roles 62 const ROLE_PC = 1; 63 const ROLE_ADMIN = 2; 64 const ROLE_CHAIR = 4; 65 const ROLE_PCLIKE = 15; 66 const ROLE_AUTHOR = 16; 67 const ROLE_REVIEWER = 32; 68 const ROLE_REQUESTER = 64; 69 private $_db_roles; 70 private $_active_roles; 71 private $_has_outstanding_review; 72 private $_is_metareviewer; 73 private $_is_lead; 74 private $_is_explicit_manager; 75 private $_dangerous_track_mask; 76 private $_can_view_pc; 77 public $is_site_contact = false; 78 private $_rights_version = 0; 79 public $roles = 0; 80 public $isPC = false; 81 public $privChair = false; 82 public $contactTags; 83 public $tracker_kiosk_state = false; 84 const CAP_AUTHORVIEW = 1; 85 private $capabilities; 86 private $_review_tokens; 87 private $_activated = false; 88 const OVERRIDE_CONFLICT = 1; 89 const OVERRIDE_TIME = 2; 90 const OVERRIDE_TAG_CHECKS = 4; 91 const OVERRIDE_EDIT_CONDITIONS = 8; 92 private $_overrides = 0; 93 public $hidden_papers; 94 private $_aucollab_matchers; 95 private $_aucollab_general_pregexes; 96 private $_authored_papers; 97 98 // Per-paper DB information, usually null 99 public $conflictType; 100 public $myReviewPermissions; 101 public $watch; 102 103 static private $status_info_cache = array(); 104 105 106 function __construct($trueuser = null, Conf $conf = null) { 107 global $Conf; 108 $this->conf = $conf ? : $Conf; 109 if ($trueuser) 110 $this->merge($trueuser); 111 else if ($this->contactId || $this->contactDbId) 112 $this->db_load(); 113 else if ($this->conf->opt("disableNonPC")) 114 $this->disabled = true; 115 } 116 117 static function fetch($result, Conf $conf) { 118 $user = $result ? $result->fetch_object("Contact", [null, $conf]) : null; 119 if ($user && !is_int($user->contactId)) { 120 $user->conf = $conf; 121 $user->db_load(); 122 } 123 return $user; 124 } 125 126 private function merge($user) { 127 if (is_array($user)) 128 $user = (object) $user; 129 if (!isset($user->dsn) || $user->dsn == $this->conf->dsn) { 130 if (isset($user->contactId)) 131 $this->contactId = (int) $user->contactId; 132 } 133 if (isset($user->contactDbId)) 134 $this->contactDbId = (int) $user->contactDbId; 135 if (isset($user->firstName) && isset($user->lastName)) 136 $name = $user; 137 else 138 $name = Text::analyze_name($user); 139 $this->firstName = get_s($name, "firstName"); 140 $this->lastName = get_s($name, "lastName"); 141 if (isset($user->unaccentedName)) 142 $this->unaccentedName = $user->unaccentedName; 143 else if (isset($name->unaccentedName)) 144 $this->unaccentedName = $name->unaccentedName; 145 else 146 $this->unaccentedName = Text::unaccented_name($name); 147 foreach (["email", "preferredEmail", "affiliation", "phone", 148 "country", "birthday", "gender"] as $k) 149 if (isset($user->$k)) 150 $this->$k = simplify_whitespace($user->$k); 151 if (isset($user->collaborators)) 152 $this->collaborators = $user->collaborators; 153 self::set_sorter($this, $this->conf); 154 if (isset($user->password)) 155 $this->password = (string) $user->password; 156 if (isset($user->disabled)) 157 $this->disabled = !!$user->disabled; 158 foreach (["defaultWatch", "passwordTime", "passwordUseTime", 159 "updateTime", "creationTime"] as $k) 160 if (isset($user->$k)) 161 $this->$k = (int) $user->$k; 162 if (property_exists($user, "contactTags")) 163 $this->contactTags = $user->contactTags; 164 else 165 $this->contactTags = $this->contactId ? false : null; 166 if (isset($user->activity_at)) 167 $this->activity_at = (int) $user->activity_at; 168 else if (isset($user->lastLogin)) 169 $this->activity_at = (int) $user->lastLogin; 170 if (isset($user->birthday)) 171 $this->birthday = (int) $user->birthday; 172 if (isset($user->data) && $user->data) 173 // this works even if $user->data is a JSON string 174 // (array_to_object_recursive($str) === $str) 175 $this->data = array_to_object_recursive($user->data); 176 if (isset($user->roles) || isset($user->isPC) || isset($user->isAssistant) 177 || isset($user->isChair)) { 178 $roles = (int) get($user, "roles"); 179 if (get($user, "isPC")) 180 $roles |= self::ROLE_PC; 181 if (get($user, "isAssistant")) 182 $roles |= self::ROLE_ADMIN; 183 if (get($user, "isChair")) 184 $roles |= self::ROLE_CHAIR; 185 $this->assign_roles($roles); 186 } 187 if (!$this->isPC && $this->conf->opt("disableNonPC")) 188 $this->disabled = true; 189 if (isset($user->has_review)) 190 $this->has_review_ = $user->has_review; 191 if (isset($user->has_outstanding_review)) 192 $this->_has_outstanding_review = $user->has_outstanding_review; 193 if (isset($user->is_site_contact)) 194 $this->is_site_contact = $user->is_site_contact; 195 } 196 197 private function db_load() { 198 $this->contactId = (int) $this->contactId; 199 $this->contactDbId = (int) $this->contactDbId; 200 if ($this->unaccentedName === "") 201 $this->unaccentedName = Text::unaccented_name($this->firstName, $this->lastName); 202 self::set_sorter($this, $this->conf); 203 $this->password = (string) $this->password; 204 if (isset($this->disabled)) 205 $this->disabled = !!$this->disabled; 206 foreach (["defaultWatch", "passwordTime", "passwordUseTime", 207 "updateTime", "creationTime"] as $k) 208 $this->$k = (int) $this->$k; 209 if (!$this->activity_at && isset($this->lastLogin)) 210 $this->activity_at = (int) $this->lastLogin; 211 if (isset($this->birthday)) 212 $this->birthday = (int) $this->birthday; 213 if ($this->data) 214 // this works even if $user->data is a JSON string 215 // (array_to_object_recursive($str) === $str) 216 $this->data = array_to_object_recursive($this->data); 217 if (isset($this->roles)) 218 $this->assign_roles((int) $this->roles); 219 if (isset($this->__isAuthor__)) 220 $this->_db_roles = ((int) $this->__isAuthor__ > 0 ? self::ROLE_AUTHOR : 0) 221 | ((int) $this->__hasReview__ > 0 ? self::ROLE_REVIEWER : 0); 222 if (!$this->isPC && $this->conf->opt("disableNonPC")) 223 $this->disabled = true; 224 } 225 226 function merge_secondary_properties($x) { 227 foreach (["preferredEmail", "phone", "country", "password", 228 "collaborators", "birthday", "gender"] as $k) 229 if (isset($x->$k)) 230 $this->$k = $x->$k; 231 foreach (["passwordTime", "passwordUseTime", "creationTime", 232 "updateTime", "defaultWatch"] as $k) 233 if (isset($x->$k)) 234 $this->$k = (int) $x->$k; 235 if (isset($x->lastLogin)) 236 $this->activity_at = $this->lastLogin = (int) $x->lastLogin; 237 if ($x->data) 238 $this->data = array_to_object_recursive($x->data); 239 } 240 241 function __set($name, $value) { 242 if (!self::$allow_nonexistent_properties) 243 error_log(caller_landmark(1) . ": writing nonexistent property $name"); 244 $this->$name = $value; 245 } 246 247 static function set_sorter($c, Conf $conf) { 248 if (!$conf->sort_by_last && isset($c->unaccentedName)) { 249 $c->sorter = trim("$c->unaccentedName $c->email"); 250 return; 251 } 252 if ($conf->sort_by_last) { 253 if (($m = Text::analyze_von($c->lastName))) 254 $c->sorter = trim("$m[1] $c->firstName $m[0] $c->email"); 255 else 256 $c->sorter = trim("$c->lastName $c->firstName $c->email"); 257 } else 258 $c->sorter = trim("$c->firstName $c->lastName $c->email"); 259 if (preg_match('/[\x80-\xFF]/', $c->sorter)) 260 $c->sorter = UnicodeHelper::deaccent($c->sorter); 261 } 262 263 static function compare($a, $b) { 264 return strnatcasecmp($a->sorter, $b->sorter); 265 } 266 267 private function assign_roles($roles) { 268 $this->roles = $roles; 269 $this->isPC = ($roles & self::ROLE_PCLIKE) != 0; 270 $this->privChair = ($roles & (self::ROLE_ADMIN | self::ROLE_CHAIR)) != 0; 271 } 272 273 274 // initialization 275 276 private function actas_user($x, $trueuser) { 277 // translate to email 278 if (is_numeric($x)) { 279 $acct = $this->conf->user_by_id($x); 280 $email = $acct ? $acct->email : null; 281 } else if ($x === "admin") 282 $email = $trueuser->email; 283 else 284 $email = $x; 285 if (!$email || strcasecmp($email, $this->email) == 0) 286 return $this; 287 288 // can always turn back into baseuser 289 $baseuser = $this; 290 if (strcasecmp($this->email, $trueuser->email) != 0 291 && ($u = $this->conf->user_by_email($trueuser->email))) 292 $baseuser = $u; 293 if (strcasecmp($email, $baseuser->email) == 0) 294 return $baseuser; 295 296 // cannot actas unless chair 297 if (!$this->privChair && !$baseuser->privChair) 298 return $this; 299 300 // new account must exist 301 $u = $this->conf->user_by_email($email); 302 if (!$u && validate_email($email) && get($this->conf->opt, "debugShowSensitiveEmail")) 303 $u = Contact::create($this->conf, null, ["email" => $email]); 304 if (!$u) 305 return $this; 306 307 // cannot turn into a manager of conflicted papers 308 if ($this->conf->setting("papermanager")) { 309 $result = $this->conf->qe("select paperId from Paper join PaperConflict using (paperId) where managerContactId!=0 and managerContactId!=? and PaperConflict.contactId=? and conflictType>0", $this->contactId, $this->contactId); 310 while (($row = $result->fetch_row())) 311 $u->hidden_papers[(int) $row[0]] = false; 312 Dbl::free($result); 313 } 314 315 // otherwise ok 316 return $u; 317 } 318 319 function activate($qreq) { 320 global $Now; 321 $this->_activated = true; 322 $trueuser = isset($_SESSION["trueuser"]) ? $_SESSION["trueuser"] : null; 323 $truecontact = null; 324 325 // Handle actas requests 326 if ($qreq && $qreq->actas && $trueuser) { 327 $actas = $qreq->actas; 328 unset($qreq->actas, $_GET["actas"], $_POST["actas"]); 329 $actascontact = $this->actas_user($actas, $trueuser); 330 if ($actascontact !== $this) { 331 if ($actascontact->email !== $trueuser->email) { 332 hoturl_defaults(array("actas" => $actascontact->email)); 333 $_SESSION["last_actas"] = $actascontact->email; 334 } 335 if ($this->privChair) 336 self::$trueuser_privChair = $actascontact; 337 return $actascontact->activate($qreq); 338 } 339 } 340 341 // Handle invalidate-caches requests 342 if ($qreq && $qreq->invalidatecaches && $this->privChair) { 343 unset($qreq->invalidatecaches); 344 $this->conf->invalidate_caches(); 345 } 346 347 // Add capabilities from session and request 348 if (!$this->conf->opt("disableCapabilities")) { 349 if (($caps = $this->conf->session("capabilities"))) { 350 $this->capabilities = $caps; 351 ++self::$rights_version; 352 } 353 if ($qreq && (isset($qreq->cap) || isset($qreq->testcap))) 354 $this->activate_capabilities($qreq); 355 } 356 357 // Add review tokens from session 358 if (($rtokens = $this->conf->session("rev_tokens"))) { 359 $this->_review_tokens = $rtokens; 360 ++self::$rights_version; 361 } 362 363 // Maybe auto-create a user 364 if ($trueuser 365 && strcasecmp($trueuser->email, $this->email) == 0) { 366 $trueuser_aucheck = $this->conf->session("trueuser_author_check", 0); 367 if (!$this->has_database_account() 368 && $trueuser_aucheck + 600 < $Now) { 369 $this->conf->save_session("trueuser_author_check", $Now); 370 $aupapers = self::email_authored_papers($this->conf, $this->email, $this); 371 if (!empty($aupapers)) 372 $this->activate_database_account(); 373 } 374 if ($this->has_database_account() 375 && $trueuser_aucheck) { 376 foreach ($_SESSION as $k => $v) { 377 if (is_array($v) 378 && isset($v["trueuser_author_check"]) 379 && $v["trueuser_author_check"] + 600 < $Now) 380 unset($_SESSION[$k]["trueuser_author_check"]); 381 } 382 } 383 } 384 385 // Maybe set up the shared contacts database 386 if ($this->conf->opt("contactdb_dsn") 387 && $this->has_database_account() 388 && $this->conf->session("contactdb_roles", 0) != $this->contactdb_roles()) { 389 if ($this->contactdb_update()) 390 $this->conf->save_session("contactdb_roles", $this->contactdb_roles()); 391 } 392 393 // Check forceShow 394 $this->_overrides = 0; 395 if ($qreq && $qreq->forceShow && $this->privChair) 396 $this->_overrides |= self::OVERRIDE_CONFLICT; 397 if ($qreq && $qreq->override) 398 $this->_overrides |= self::OVERRIDE_TIME; 399 400 return $this; 401 } 402 403 function overrides() { 404 return $this->_overrides; 405 } 406 function set_overrides($overrides) { 407 $old_overrides = $this->_overrides; 408 if (!$this->privChair) 409 $overrides &= ~self::OVERRIDE_CONFLICT; 410 $this->_overrides = $overrides; 411 return $old_overrides; 412 } 413 function add_overrides($overrides) { 414 return $this->set_overrides($this->_overrides | $overrides); 415 } 416 function remove_overrides($overrides) { 417 return $this->set_overrides($this->_overrides & ~$overrides); 418 } 419 function call_with_overrides($overrides, $method /* , arguments... */) { 420 $old_overrides = $this->set_overrides($overrides); 421 $result = call_user_func_array([$this, $method], array_slice(func_get_args(), 2)); 422 $this->_overrides = $old_overrides; 423 return $result; 424 } 425 426 function activate_database_account() { 427 assert($this->has_email()); 428 if (!$this->has_database_account() 429 && ($u = Contact::create($this->conf, null, $this))) { 430 $this->merge($u); 431 $this->contactDbId = 0; 432 $this->_contactdb_user = false; 433 $this->activate(null); 434 } 435 } 436 437 function contactdb_user($refresh = false) { 438 if ($this->contactDbId && !$this->contactId) 439 return $this; 440 else if ($refresh || $this->_contactdb_user === false) { 441 $cdbu = null; 442 if ($this->has_email()) 443 $cdbu = $this->conf->contactdb_user_by_email($this->email); 444 $this->_contactdb_user = $cdbu; 445 } 446 return $this->_contactdb_user; 447 } 448 449 private function _contactdb_save_roles($cdbur) { 450 global $Now; 451 Dbl::ql($this->conf->contactdb(), "insert into Roles set contactDbId=?, confid=?, roles=?, activity_at=? on duplicate key update roles=values(roles), activity_at=values(activity_at)", $cdbur->contactDbId, $cdbur->confid, $this->contactdb_roles(), $Now); 452 } 453 function contactdb_update($update_keys = null, $only_update_empty = false) { 454 global $Now; 455 if (!($cdb = $this->conf->contactdb()) 456 || !$this->has_database_account() 457 || !validate_email($this->email)) 458 return false; 459 460 $cdbur = $this->conf->contactdb_user_by_email($this->email); 461 $cdbux = $cdbur ? : new Contact(null, $this->conf); 462 $upd = []; 463 foreach (["firstName", "lastName", "affiliation", "country", "collaborators", 464 "birthday", "gender"] as $k) 465 if ($this->$k !== null 466 && $this->$k !== "" 467 && (!$only_update_empty || $cdbux->$k === null || $cdbux->$k === "") 468 && (!$cdbur || in_array($k, $update_keys ? : []))) 469 $upd[$k] = $this->$k; 470 if (!$cdbur) { 471 $upd["email"] = $this->email; 472 if ($this->password 473 && $this->password !== "*" 474 && ($this->password[0] !== " " || $this->password[1] === "\$")) { 475 $upd["password"] = $this->password; 476 $upd["passwordTime"] = $this->passwordTime; 477 } 478 } 479 if (!empty($upd)) { 480 $cdbux->apply_updater($upd, true); 481 $this->_contactdb_user = false; 482 } 483 $cdbur = $cdbur ? : $this->conf->contactdb_user_by_email($this->email); 484 if ($cdbur->confid 485 && (int) $cdbur->roles !== $this->contactdb_roles()) 486 $this->_contactdb_save_roles($cdbur); 487 return $cdbur ? (int) $cdbur->contactDbId : false; 488 } 489 490 function is_actas_user() { 491 return $this->_activated 492 && isset($_SESSION["trueuser"]) 493 && strcasecmp($_SESSION["trueuser"]->email, $this->email) !== 0; 494 } 495 496 private function activate_capabilities($qreq) { 497 // Add capabilities from arguments 498 if (($cap_req = $qreq->cap)) { 499 foreach (preg_split(',\s+,', $cap_req) as $cap) 500 $this->apply_capability_text($cap); 501 unset($qreq->cap, $_GET["cap"], $_POST["cap"]); 502 } 503 504 // Support capability testing 505 if ($this->conf->opt("testCapabilities") 506 && ($cap_req = $qreq->testcap) 507 && preg_match_all('/([-+]?)([1-9]\d*)([A-Za-z]+)/', 508 $cap_req, $m, PREG_SET_ORDER)) { 509 foreach ($m as $mm) { 510 $c = ($mm[3] == "a" ? self::CAP_AUTHORVIEW : 0); 511 $this->change_paper_capability((int) $mm[2], $c, $mm[1] !== "-"); 512 } 513 unset($qreq->testcap, $_GET["testcap"], $_POST["testcap"]); 514 } 515 } 516 517 function is_empty() { 518 return $this->contactId <= 0 && !$this->capabilities && !$this->email; 519 } 520 521 function owns_email($email) { 522 return (string) $email !== "" && strcasecmp($email, $this->email) === 0; 523 } 524 525 function name_text() { 526 if ($this->firstName === "" || $this->lastName === "") 527 return $this->firstName . $this->lastName; 528 else 529 return $this->firstName . " " . $this->lastName; 530 } 531 532 function completion_items() { 533 $items = []; 534 535 $x = strtolower(substr($this->email, 0, strpos($this->email, "@"))); 536 if ($x !== "") 537 $items[$x] = 2; 538 539 $sp = strpos($this->firstName, " ") ? : strlen($this->firstName); 540 $x = strtolower(UnicodeHelper::deaccent(substr($this->firstName, 0, $sp))); 541 if ($x !== "" && ctype_alnum($x)) 542 $items[$x] = 1; 543 544 $sp = strrpos($this->lastName, " "); 545 $x = strtolower(UnicodeHelper::deaccent(substr($this->lastName, $sp ? $sp + 1 : 0))); 546 if ($x !== "" && ctype_alnum($x)) 547 $items[$x] = 1; 548 549 return $items; 550 } 551 552 private function calculate_name_for($pfx, $user) { 553 if ($pfx === "u") 554 return $user; 555 if ($pfx === "t") 556 return Text::name_text($user); 557 $n = Text::name_html($user); 558 if ($pfx === "r" && isset($user->contactTags) 559 && ($colors = $this->user_color_classes_for($user))) 560 $n = '<span class="' . $colors . ' taghh">' . $n . '</span>'; 561 return $n; 562 } 563 564 private function name_for($pfx, $x) { 565 $cid = is_object($x) ? $x->contactId : $x; 566 $key = $pfx . $cid; 567 if (isset($this->_name_for_map[$key])) 568 return $this->_name_for_map[$key]; 569 570 if (+$cid === $this->contactId) 571 $x = $this; 572 else if (($pc = $this->conf->pc_member_by_id($cid))) 573 $x = $pc; 574 575 if (!(is_object($x) && isset($x->firstName) && isset($x->lastName) && isset($x->email))) { 576 if ($pfx === "u") { 577 $x = $this->conf->user_by_id($cid); 578 $this->_contact_sorter_map[$cid] = $x->sorter; 579 } else 580 $x = $this->name_for("u", $x); 581 } 582 583 return ($this->_name_for_map[$key] = $this->calculate_name_for($pfx, $x)); 584 } 585 586 function name_html_for($x) { 587 return $this->name_for("", $x); 588 } 589 590 function name_text_for($x) { 591 return $this->name_for("t", $x); 592 } 593 594 function name_object_for($x) { 595 return $this->name_for("u", $x); 596 } 597 598 function reviewer_html_for($x) { 599 return $this->name_for($this->isPC ? "r" : "", $x); 600 } 601 602 function reviewer_text_for($x) { 603 return $this->name_for("t", $x); 604 } 605 606 function user_color_classes_for(Contact $x) { 607 return $x->viewable_color_classes($this); 608 } 609 610 function ksort_cid_array(&$a) { 611 $pcm = $this->conf->pc_members(); 612 uksort($a, function ($a, $b) use ($pcm) { 613 if (isset($pcm[$a]) && isset($pcm[$b])) 614 return $pcm[$a]->sort_position - $pcm[$b]->sort_position; 615 if (isset($pcm[$a])) 616 $as = $pcm[$a]->sorter; 617 else if (isset($this->_contact_sorter_map[$a])) 618 $as = $this->_contact_sorter_map[$a]; 619 else { 620 $x = $this->conf->user_by_id($a); 621 $as = $this->_contact_sorter_map[$a] = $x->sorter; 622 } 623 if (isset($pcm[$b])) 624 $bs = $pcm[$b]->sorter; 625 else if (isset($this->_contact_sorter_map[$b])) 626 $bs = $this->_contact_sorter_map[$b]; 627 else { 628 $x = $this->conf->user_by_id($b); 629 $bs = $this->_contact_sorter_map[$b] = $x->sorter; 630 } 631 return strcasecmp($as, $bs); 632 }); 633 } 634 635 function has_email() { 636 return !!$this->email; 637 } 638 639 static function is_anonymous_email($email) { 640 // see also PaperSearch, Mailer 641 return substr($email, 0, 9) === "anonymous" 642 && (strlen($email) === 9 || ctype_digit(substr($email, 9))); 643 } 644 645 function is_anonymous_user() { 646 return $this->email && self::is_anonymous_email($this->email); 647 } 648 649 function has_database_account() { 650 return $this->contactId > 0; 651 } 652 653 function is_admin() { 654 return $this->privChair; 655 } 656 657 function is_admin_force() { 658 return ($this->_overrides & self::OVERRIDE_CONFLICT) !== 0; 659 } 660 661 function is_pc_member() { 662 return $this->roles & self::ROLE_PC; 663 } 664 665 function is_pclike() { 666 return $this->roles & self::ROLE_PCLIKE; 667 } 668 669 function role_html() { 670 if ($this->roles & (Contact::ROLE_CHAIR | Contact::ROLE_ADMIN | Contact::ROLE_PC)) { 671 if ($this->roles & Contact::ROLE_CHAIR) 672 return '<span class="pcrole">chair</span>'; 673 else if (($this->roles & (Contact::ROLE_ADMIN | Contact::ROLE_PC)) == (Contact::ROLE_ADMIN | Contact::ROLE_PC)) 674 return '<span class="pcrole">PC, sysadmin</span>'; 675 else if ($this->roles & Contact::ROLE_ADMIN) 676 return '<span class="pcrole">sysadmin</span>'; 677 else 678 return '<span class="pcrole">PC</span>'; 679 } else 680 return ''; 681 } 682 683 function has_tag($t) { 684 if (($this->roles & self::ROLE_PC) && strcasecmp($t, "pc") == 0) 685 return true; 686 if ($this->contactTags) 687 return stripos($this->contactTags, " $t#") !== false; 688 if ($this->contactTags === false) { 689 trigger_error(caller_landmark(1, "/^Conf::/") . ": Contact $this->email contactTags missing " . json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS))); 690 $this->contactTags = null; 691 } 692 return false; 693 } 694 695 function tag_value($t) { 696 if (($this->roles & self::ROLE_PC) && strcasecmp($t, "pc") == 0) 697 return 0.0; 698 if ($this->contactTags 699 && ($p = stripos($this->contactTags, " $t#")) !== false) 700 return (float) substr($this->contactTags, $p + strlen($t) + 2); 701 return false; 702 } 703 704 static function roles_all_contact_tags($roles, $tags) { 705 $t = ""; 706 if ($roles & self::ROLE_PC) 707 $t = " pc#0"; 708 if ($tags) 709 return $t . $tags; 710 else 711 return $t ? $t . " " : ""; 712 } 713 714 function all_contact_tags() { 715 return self::roles_all_contact_tags($this->roles, $this->contactTags); 716 } 717 718 function viewable_tags(Contact $viewer) { 719 if ($viewer->can_view_contact_tags() || $viewer->contactId == $this->contactId) { 720 $tags = $this->all_contact_tags(); 721 return $this->conf->tags()->strip_nonviewable($tags, $viewer, null); 722 } else 723 return ""; 724 } 725 726 function viewable_color_classes(Contact $viewer) { 727 if ($viewer->isPC && ($tags = $this->viewable_tags($viewer))) 728 return $this->conf->tags()->color_classes($tags); 729 else 730 return ""; 731 } 732 733 private function update_capabilities() { 734 ++self::$rights_version; 735 if (empty($this->capabilities)) 736 $this->capabilities = null; 737 if ($this->_activated) 738 $this->conf->save_session("capabilities", $this->capabilities); 739 } 740 741 function capability($name) { 742 if ($this->capabilities !== null && isset($this->capabilities[0])) 743 return get($this->capabilities[0], $name); 744 else 745 return null; 746 } 747 748 function set_capability($name, $newval) { 749 $oldval = $this->capability($name); 750 if ($newval !== $oldval) { 751 ++self::$rights_version; 752 if ($newval !== null) 753 $this->capabilities[0][$name] = $newval; 754 else 755 unset($this->capabilities[0][$name]); 756 if (empty($this->capabilities[0])) 757 unset($this->capabilities[0]); 758 $this->update_capabilities(); 759 } 760 return $newval !== $oldval; 761 } 762 763 function change_paper_capability($pid, $bit, $isset) { 764 $oldval = 0; 765 if ($this->capabilities !== null) 766 $oldval = get($this->capabilities, $pid) ? : 0; 767 $newval = ($oldval & ~$bit) | ($isset ? $bit : 0); 768 if ($newval !== $oldval) { 769 if ($newval !== 0) 770 $this->capabilities[$pid] = $newval; 771 else 772 unset($this->capabilities[$pid]); 773 $this->update_capabilities(); 774 } 775 return $newval !== $oldval; 776 } 777 778 function apply_capability_text($text) { 779 if (preg_match(',\A([-+]?)0([1-9][0-9]*)(a)(\S+)\z,', $text, $m) 780 && ($result = $this->conf->ql("select paperId, capVersion from Paper where paperId=$m[2]")) 781 && ($row = edb_orow($result))) { 782 $rowcap = $this->conf->capability_text($row, $m[3]); 783 $text = substr($text, strlen($m[1])); 784 if ($rowcap === $text 785 || $rowcap === str_replace("/", "_", $text)) 786 return $this->change_paper_capability((int) $m[2], self::CAP_AUTHORVIEW, $m[1] !== "-"); 787 } 788 return null; 789 } 790 791 private function make_data() { 792 if (is_string($this->data)) 793 $this->data = json_decode($this->data); 794 if (!$this->data) 795 $this->data = (object) array(); 796 } 797 798 function data($key = null) { 799 $this->make_data(); 800 if ($key) 801 return get($this->data, $key); 802 else 803 return $this->data; 804 } 805 806 private function encode_data() { 807 if ($this->data && ($t = json_encode($this->data)) !== "{}") 808 return $t; 809 else 810 return null; 811 } 812 813 function save_data($key, $value) { 814 $this->merge_and_save_data((object) array($key => array_to_object_recursive($value))); 815 } 816 817 function merge_data($data) { 818 $this->make_data(); 819 object_replace_recursive($this->data, array_to_object_recursive($data)); 820 } 821 822 function merge_and_save_data($data) { 823 $this->activate_database_account(); 824 $this->make_data(); 825 $old = $this->encode_data(); 826 object_replace_recursive($this->data, array_to_object_recursive($data)); 827 $new = $this->encode_data(); 828 if ($old !== $new) 829 $this->conf->qe("update ContactInfo set data=? where contactId=?", $new, $this->contactId); 830 } 831 832 private function data_str() { 833 $d = null; 834 if (is_string($this->data)) 835 $d = $this->data; 836 else if (is_object($this->data)) 837 $d = json_encode($this->data); 838 return $d === "{}" ? null : $d; 839 } 840 841 function escape($qreq = null) { 842 global $Qreq, $Now; 843 $qreq = $qreq ? : $Qreq; 844 845 if ($qreq->ajax) { 846 if ($this->is_empty()) 847 json_exit(["ok" => false, "error" => "You have been signed out.", "loggedout" => true]); 848 else 849 json_exit(["ok" => false, "error" => "You don’t have permission to access that page."]); 850 } 851 852 if ($this->is_empty()) { 853 // Preserve post values across session expiration. 854 ensure_session(); 855 $x = array(); 856 if (Navigation::path()) 857 $x["__PATH__"] = preg_replace(",^/+,", "", Navigation::path()); 858 if ($qreq->anchor) 859 $x["anchor"] = $qreq->anchor; 860 $url = SelfHref::make($qreq, $x, ["raw" => true, "site_relative" => true]); 861 $_SESSION["login_bounce"] = [$this->conf->dsn, $url, Navigation::page(), $_POST, $Now + 120]; 862 if ($qreq->post_ok()) 863 error_go(false, "You’ve been signed out, so your changes were not saved. After signing in, you may submit them again."); 864 else 865 error_go(false, "You must sign in to access that page."); 866 } else 867 error_go(false, "You don’t have permission to access that page."); 868 } 869 870 871 static private $cdb_fields = [ 872 "firstName" => true, "lastName" => true, "affiliation" => true, 873 "country" => true, "collaborators" => true, "birthday" => true, 874 "gender" => true 875 ]; 876 static private $no_clean_fields = [ 877 "collaborators" => true, "defaultWatch" => true, "contactTags" => true 878 ]; 879 880 private function _save_assign_field($k, $v, Contact_Update $cu) { 881 if (!isset(self::$no_clean_fields[$k])) { 882 $v = simplify_whitespace($v); 883 if ($k === "birthday" && !$v) 884 $v = null; 885 } 886 // change contactdb 887 if (isset(self::$cdb_fields[$k]) 888 && ($this->$k !== $v || $cu->changing_email)) 889 $cu->cdb_qf[] = $k; 890 // change local version 891 if ($this->$k !== $v || !$this->contactId) 892 $cu->qv[$k] = $v; 893 $this->$k = $v; 894 } 895 896 static function parse_roles_json($j) { 897 $roles = 0; 898 if (isset($j->pc) && $j->pc) 899 $roles |= self::ROLE_PC; 900 if (isset($j->chair) && $j->chair) 901 $roles |= self::ROLE_CHAIR | self::ROLE_PC; 902 if (isset($j->sysadmin) && $j->sysadmin) 903 $roles |= self::ROLE_ADMIN; 904 return $roles; 905 } 906 907 const SAVE_NOTIFY = 1; 908 const SAVE_ANY_EMAIL = 2; 909 const SAVE_IMPORT = 4; 910 const SAVE_NO_EXPORT = 8; 911 function save_json($cj, $actor, $flags) { 912 global $Me, $Now; 913 assert(!!$this->contactId); 914 $old_roles = $this->roles; 915 $old_email = $this->email; 916 $old_disabled = $this->disabled ? 1 : 0; 917 $changing_email = isset($cj->email) && strtolower($cj->email) !== strtolower((string) $old_email); 918 $cu = new Contact_Update($changing_email); 919 920 $aupapers = null; 921 if ($changing_email) 922 $aupapers = self::email_authored_papers($this->conf, $cj->email, $cj); 923 924 // check whether this user is changing themselves 925 $changing_other = false; 926 if ($this->conf->contactdb() 927 && $Me 928 && (strcasecmp($this->email, $Me->email) != 0 || $Me->is_actas_user())) 929 $changing_other = true; 930 931 // Main fields 932 foreach (["firstName", "lastName", "email", "affiliation", "collaborators", 933 "preferredEmail", "country", "birthday", "gender", "phone"] as $k) { 934 if (isset($cj->$k)) 935 $this->_save_assign_field($k, $cj->$k, $cu); 936 } 937 if (isset($cj->preferred_email) && !isset($cj->preferredEmail)) 938 $this->_save_assign_field("preferredEmail", $cj->preferred_email, $cu); 939 $this->_save_assign_field("unaccentedName", Text::unaccented_name($this->firstName, $this->lastName), $cu); 940 self::set_sorter($this, $this->conf); 941 942 // Disabled 943 $disabled = $old_disabled; 944 if (isset($cj->disabled)) 945 $disabled = $cj->disabled ? 1 : 0; 946 if ($disabled !== $old_disabled || !$this->contactId) 947 $cu->qv["disabled"] = $this->disabled = $disabled; 948 949 // Data 950 $old_datastr = $this->data_str(); 951 $data = get($cj, "data", (object) array()); 952 foreach (array("address", "city", "state", "zip") as $k) 953 if (isset($cj->$k) && ($x = $cj->$k)) { 954 while (is_array($x) && $x[count($x) - 1] === "") 955 array_pop($x); 956 $data->$k = $x ? : null; 957 } 958 $this->merge_data($data); 959 $datastr = $this->data_str(); 960 if ($datastr !== $old_datastr) 961 $cu->qv["data"] = $datastr; 962 963 // Changes to the above fields also change the updateTime. 964 if (!empty($cu->qv)) 965 $cu->qv["updateTime"] = $this->updateTime = $Now; 966 967 // Follow 968 if (isset($cj->follow)) { 969 $w = 0; 970 if (get($cj->follow, "reviews")) 971 $w |= self::WATCH_REVIEW; 972 if (get($cj->follow, "allreviews")) 973 $w |= self::WATCH_REVIEW_ALL; 974 if (get($cj->follow, "managedreviews")) 975 $w |= self::WATCH_REVIEW_MANAGED; 976 if (get($cj->follow, "allfinal")) 977 $w |= self::WATCH_FINAL_SUBMIT_ALL; 978 $this->_save_assign_field("defaultWatch", $w, $cu); 979 } 980 981 // Tags 982 if (isset($cj->tags)) { 983 $tags = array(); 984 foreach ($cj->tags as $t) { 985 list($tag, $value) = TagInfo::unpack($t); 986 if (strcasecmp($tag, "pc") != 0) 987 $tags[$tag] = $tag . "#" . ($value ? : 0); 988 } 989 ksort($tags); 990 $t = count($tags) ? " " . join(" ", $tags) . " " : ""; 991 $this->_save_assign_field("contactTags", $t, $cu); 992 } 993 994 // Initial save 995 if (count($cu->qv)) { // always true if $inserting 996 $q = "update ContactInfo set " 997 . join("=?, ", array_keys($cu->qv)) . "=?" 998 . " where contactId=$this->contactId"; 999 if (!($result = $this->conf->qe_apply($q, array_values($cu->qv)))) 1000 return $result; 1001 Dbl::free($result); 1002 } 1003 1004 // Topics 1005 if (isset($cj->topics)) { 1006 $tf = array(); 1007 foreach ($cj->topics as $k => $v) 1008 if ($v || empty($tf)) 1009 $tf[] = "($this->contactId,$k,$v)"; 1010 $this->conf->qe_raw("delete from TopicInterest where contactId=$this->contactId"); 1011 if (!empty($tf)) 1012 $this->conf->qe_raw("insert into TopicInterest (contactId,topicId,interest) values " . join(",", $tf)); 1013 $this->_topic_interest_map = null; 1014 } 1015 1016 // Roles 1017 $roles = $old_roles; 1018 if (isset($cj->roles)) { 1019 $roles = self::parse_roles_json($cj->roles); 1020 if ($roles !== $old_roles) 1021 $this->save_roles($roles, $actor); 1022 } 1023 1024 // Update authorship 1025 if ($aupapers) 1026 $this->save_authored_papers($aupapers); 1027 1028 // Contact DB (must precede password) 1029 $cdb = $this->conf->contactdb(); 1030 if ($changing_email) 1031 $this->_contactdb_user = false; 1032 if ($cdb && !($flags & self::SAVE_NO_EXPORT) 1033 && (!empty($cu->cdb_qf) || $roles !== $old_roles)) 1034 $this->contactdb_update($cu->cdb_qf, $changing_other); 1035 1036 // Password 1037 if (isset($cj->new_password)) 1038 $this->change_password($cj->new_password, 0); 1039 1040 // Beware PC cache 1041 if (($roles | $old_roles) & Contact::ROLE_PCLIKE) 1042 $this->conf->invalidate_caches(["pc" => 1]); 1043 1044 $actor = $actor ? : $Me; 1045 if ($actor && $this->contactId == $actor->contactId) 1046 $this->mark_activity(); 1047 1048 return true; 1049 } 1050 1051 function change_email($email) { 1052 assert($this->has_database_account()); 1053 $aupapers = self::email_authored_papers($this->conf, $email, $this); 1054 $this->conf->ql("update ContactInfo set email=? where contactId=?", $email, $this->contactId); 1055 $this->save_authored_papers($aupapers); 1056 if ($this->roles & Contact::ROLE_PCLIKE) 1057 $this->conf->invalidate_caches(["pc" => 1]); 1058 $this->email = $email; 1059 } 1060 1061 static function email_authored_papers(Conf $conf, $email, $reg) { 1062 $aupapers = array(); 1063 $result = $conf->q("select paperId, authorInformation from Paper where authorInformation like " . Dbl::utf8ci("'%\t" . sqlq_for_like($email) . "\t%'")); 1064 while (($row = PaperInfo::fetch($result, null, $conf))) { 1065 foreach ($row->author_list() as $au) { 1066 if (strcasecmp($au->email, $email) == 0) { 1067 $aupapers[] = $row->paperId; 1068 if ($reg 1069 && ($au->firstName !== "" || $au->lastName !== "") 1070 && !isset($reg->firstName) 1071 && !isset($reg->lastName)) { 1072 $reg->firstName = $au->firstName; 1073 $reg->lastName = $au->lastName; 1074 } 1075 if ($reg 1076 && $au->affiliation !== "" 1077 && !isset($reg->affiliation)) { 1078 $reg->affiliation = $au->affiliation; 1079 } 1080 } 1081 } 1082 } 1083 return $aupapers; 1084 } 1085 1086 private function save_authored_papers($aupapers) { 1087 if (!empty($aupapers) && $this->contactId) { 1088 $this->conf->ql("insert into PaperConflict (paperId, contactId, conflictType) values ?v on duplicate key update conflictType=greatest(conflictType, " . CONFLICT_AUTHOR . ")", array_map(function ($pid) { 1089 return [$pid, $this->contactId, CONFLICT_AUTHOR]; 1090 }, $aupapers)); 1091 } 1092 } 1093 1094 function save_roles($new_roles, $actor) { 1095 $old_roles = $this->roles; 1096 // ensure there's at least one system administrator 1097 if (!($new_roles & self::ROLE_ADMIN) && ($old_roles & self::ROLE_ADMIN) 1098 && !(($result = $this->conf->qe("select contactId from ContactInfo where roles!=0 and (roles&" . self::ROLE_ADMIN . ")!=0 and contactId!=" . $this->contactId . " limit 1")) 1099 && edb_nrows($result) > 0)) 1100 $new_roles |= self::ROLE_ADMIN; 1101 // log role change 1102 foreach (array(self::ROLE_PC => "pc", 1103 self::ROLE_ADMIN => "sysadmin", 1104 self::ROLE_CHAIR => "chair") as $role => $type) 1105 if (($new_roles & $role) && !($old_roles & $role)) 1106 $this->conf->log_for($actor ? : $this, $this, "Added as $type"); 1107 else if (!($new_roles & $role) && ($old_roles & $role)) 1108 $this->conf->log_for($actor ? : $this, $this, "Removed as $type"); 1109 // save the roles bits 1110 if ($old_roles != $new_roles) { 1111 $this->conf->qe("update ContactInfo set roles=$new_roles where contactId=$this->contactId"); 1112 $this->assign_roles($new_roles); 1113 } 1114 return $old_roles != $new_roles; 1115 } 1116 1117 private function _make_create_updater($reg, $is_cdb) { 1118 $cj = []; 1119 if ($this->firstName === "" && $this->lastName === "") { 1120 if (get_s($reg, "firstName") !== "") 1121 $cj["firstName"] = (string) $reg->firstName; 1122 if (get_s($reg, "lastName") !== "") 1123 $cj["lastName"] = (string) $reg->lastName; 1124 } 1125 foreach (["affiliation", "country", "gender", "birthday", 1126 "preferredEmail", "phone"] as $k) { 1127 if ((string) $this->$k === "" 1128 && isset($reg->$k) 1129 && $reg->$k !== "") 1130 $cj[$k] = (string) $reg->$k; 1131 } 1132 if ($is_cdb ? !$this->contactDbId : !$this->contactId) 1133 $cj["email"] = $reg->email; 1134 return $cj; 1135 } 1136 1137 function apply_updater($updater, $is_cdb) { 1138 global $Now; 1139 if ($is_cdb) { 1140 $db = $this->conf->contactdb(); 1141 $idk = "contactDbId"; 1142 } else { 1143 $db = $this->conf->dblink; 1144 $idk = "contactId"; 1145 if (isset($updater["firstName"]) || isset($updater["lastName"])) { 1146 $updater["firstName"] = get($updater, "firstName", $this->firstName); 1147 $updater["lastName"] = get($updater, "lastName", $this->lastName); 1148 $updater["unaccentedName"] = Text::unaccented_name($updater["firstName"], $updater["lastName"]); 1149 } 1150 } 1151 if ($this->$idk) { 1152 $qv = array_values($updater); 1153 $qv[] = $this->$idk; 1154 $result = Dbl::qe_apply($db, "update ContactInfo set " . join("=?, ", array_keys($updater)) . "=? where $idk=?", $qv); 1155 } else { 1156 assert(isset($updater["email"])); 1157 if (!isset($updater["password"])) { 1158 $updater["password"] = validate_email($updater["email"]) ? self::random_password() : "*"; 1159 $updater["passwordTime"] = $Now; 1160 } 1161 if (!$is_cdb) 1162 $updater["creationTime"] = $Now; 1163 $result = Dbl::qe_apply($db, "insert into ContactInfo set " . join("=?, ", array_keys($updater)) . "=? on duplicate key update firstName=firstName", array_values($updater)); 1164 if ($result) 1165 $updater[$idk] = (int) $result->insert_id; 1166 } 1167 if (($ok = !!$result)) { 1168 foreach ($updater as $k => $v) 1169 $this->$k = $v; 1170 } 1171 Dbl::free($result); 1172 return $ok; 1173 } 1174 1175 static function create(Conf $conf, $actor, $reg, $flags = 0) { 1176 global $Me, $Now; 1177 1178 // clean registration 1179 if (is_array($reg)) 1180 $reg = (object) $reg; 1181 assert(is_string($reg->email)); 1182 $reg->email = trim($reg->email); 1183 assert($reg->email !== ""); 1184 if (!isset($reg->firstName) && isset($reg->first)) 1185 $reg->firstName = $reg->first; 1186 if (!isset($reg->lastName) && isset($reg->last)) 1187 $reg->lastName = $reg->last; 1188 if (isset($reg->name) && !isset($reg->firstName) && !isset($reg->lastName)) 1189 list($reg->firstName, $reg->lastName) = Text::split_name($reg->name); 1190 if (isset($reg->preferred_email) && !isset($reg->preferredEmail)) 1191 $reg->preferredEmail = $reg->preferred_email; 1192 1193 // look up existing accounts 1194 $valid_email = validate_email($reg->email); 1195 $u = $conf->user_by_email($reg->email) ? : new Contact(null, $conf); 1196 if (($cdb = $conf->contactdb()) && $valid_email) 1197 $cdbu = $conf->contactdb_user_by_email($reg->email); 1198 else 1199 $cdbu = null; 1200 $create = !$u->contactId; 1201 $aupapers = []; 1202 1203 // if local does not exist, create it 1204 if (!$u->contactId) { 1205 if (($flags & self::SAVE_IMPORT) && !$cdbu) 1206 return null; 1207 if (!$valid_email && !($flags & self::SAVE_ANY_EMAIL)) 1208 return null; 1209 if ($valid_email) 1210 // update registration from authorship information 1211 $aupapers = self::email_authored_papers($conf, $reg->email, $reg); 1212 } 1213 1214 // create or update contactdb user 1215 if ($cdb && $valid_email) { 1216 $cdbu = $cdbu ? : new Contact(null, $conf); 1217 if (($upd = $cdbu->_make_create_updater($reg, true))) 1218 $cdbu->apply_updater($upd, true); 1219 } 1220 1221 // create or update local user 1222 $upd = $u->_make_create_updater($cdbu ? : $reg, false); 1223 if (!$u->contactId) { 1224 if (($cdbu && $cdbu->disabled) || get($reg, "disabled")) 1225 $upd["disabled"] = 1; 1226 if ($cdbu) { 1227 $upd["password"] = $cdbu->password; 1228 $upd["passwordTime"] = $cdbu->passwordTime; 1229 } 1230 } 1231 if ($upd) { 1232 if (!($u->apply_updater($upd, false))) 1233 // failed because concurrent create (unlikely) 1234 $u = $conf->user_by_email($reg->email); 1235 } 1236 1237 // update paper authorship 1238 if ($aupapers) { 1239 $u->save_authored_papers($aupapers); 1240 if ($cdbu) 1241 // can't use `$cdbu` itself b/c no `confid` 1242 $u->_contactdb_save_roles($u->contactdb_user()); 1243 } 1244 1245 // notify on creation 1246 if ($create) { 1247 if (($flags & self::SAVE_NOTIFY) && !$u->disabled) 1248 $u->sendAccountInfo("create", false); 1249 $type = $u->disabled ? "disabled " : ""; 1250 $conf->log_for($actor && $actor->has_email() ? $actor : $u, $u, "Created {$type}account"); 1251 // if ($Me && $Me->privChair) 1252 // $conf->infoMsg("Created {$type}account for <a href=\"" . hoturl("profile", "u=" . urlencode($u->email)) . "\">" . Text::user_html_nolink($u) . "</a>."); 1253 } 1254 1255 return $u; 1256 } 1257 1258 1259 // PASSWORDS 1260 // 1261 // password "" or null: reset password (user must recreate password) 1262 // password "*": invalid password, cannot be reset by user 1263 // password starting with " ": legacy hashed password using hash_hmac 1264 // format: " HASHMETHOD KEYID SALT[16B]HMAC" 1265 // password starting with " $": password hashed by password_hash 1266 // 1267 // PASSWORD PRINCIPLES 1268 // 1269 // - prefer contactdb password 1270 // - require contactdb password if it is newer 1271 // 1272 // PASSWORD CHECKING RULES 1273 // 1274 // if (contactdb password exists) 1275 // check contactdb password; 1276 // if (contactdb password matches && contactdb password needs upgrade) 1277 // upgrade contactdb password; 1278 // if (contactdb password matches && local password was from contactdb) 1279 // set local password to contactdb password; 1280 // if (local password was not from contactdb || no contactdb) 1281 // check local password; 1282 // if (local password matches && local password needs upgrade) 1283 // upgrade local password; 1284 // 1285 // PASSWORD CHANGING RULES 1286 // 1287 // change(expected, new): 1288 // if (contactdb password allowed 1289 // && (!expected || expected matches contactdb)) { 1290 // change contactdb password and update time; 1291 // set local password to "*"; 1292 // } else 1293 // change local password and update time; 1294 1295 static function valid_password($input) { 1296 return $input !== "" && $input !== "0" && $input !== "*" 1297 && trim($input) === $input; 1298 } 1299 1300 static function random_password($length = 14) { 1301 return hotcrp_random_password($length); 1302 } 1303 1304 static function password_storage_cleartext() { 1305 return opt("safePasswords") < 1; 1306 } 1307 1308 function allow_contactdb_password() { 1309 $cdbu = $this->contactdb_user(); 1310 return $cdbu && $cdbu->password && $cdbu->password !== "*"; 1311 } 1312 1313 function plaintext_password() { 1314 // Return the currently active plaintext password. This might not 1315 // equal $this->password because of the cdb. 1316 if ($this->password === "" || $this->password === "*") { 1317 if ($this->contactId 1318 && ($cdbu = $this->contactdb_user())) 1319 return $cdbu->plaintext_password(); 1320 else 1321 return false; 1322 } else if ($this->password[0] === " " || $this->password === "*") 1323 return false; 1324 else 1325 return $this->password; 1326 } 1327 1328 function password_is_reset() { 1329 if (($cdbu = $this->contactdb_user())) 1330 return (string) $cdbu->password === "" 1331 && ((string) $this->password === "" 1332 || $this->passwordTime < $cdbu->passwordTime); 1333 else 1334 return $this->password === ""; 1335 } 1336 1337 function password_used() { 1338 return $this->passwordUseTime > 0; 1339 } 1340 1341 1342 // obsolete 1343 private function password_hmac_key($keyid) { 1344 if ($keyid === null) 1345 $keyid = $this->conf->opt("passwordHmacKeyid", 0); 1346 $key = $this->conf->opt("passwordHmacKey.$keyid"); 1347 if (!$key && $keyid == 0) 1348 $key = $this->conf->opt("passwordHmacKey"); 1349 if (!$key) /* backwards compatibility */ 1350 $key = $this->conf->setting_data("passwordHmacKey.$keyid"); 1351 if (!$key) { 1352 error_log("missing passwordHmacKey.$keyid, using default"); 1353 $key = "NdHHynw6JwtfSZyG3NYPTSpgPFG8UN8NeXp4tduTk2JhnSVy"; 1354 } 1355 return $key; 1356 } 1357 1358 private function check_hashed_password($input, $pwhash) { 1359 if ($input == "" 1360 || $input === "*" 1361 || (string) $pwhash === "" 1362 || $pwhash === "*") 1363 return false; 1364 else if ($pwhash[0] !== " ") 1365 return $pwhash === $input; 1366 else if ($pwhash[1] === "\$") 1367 return password_verify($input, substr($pwhash, 2)); 1368 else { 1369 if (($method_pos = strpos($pwhash, " ", 1)) !== false 1370 && ($keyid_pos = strpos($pwhash, " ", $method_pos + 1)) !== false 1371 && strlen($pwhash) > $keyid_pos + 17 1372 && function_exists("hash_hmac")) { 1373 $method = substr($pwhash, 1, $method_pos - 1); 1374 $keyid = substr($pwhash, $method_pos + 1, $keyid_pos - $method_pos - 1); 1375 $salt = substr($pwhash, $keyid_pos + 1, 16); 1376 return hash_hmac($method, $salt . $input, $this->password_hmac_key($keyid), true) 1377 == substr($pwhash, $keyid_pos + 17); 1378 } 1379 } 1380 return false; 1381 } 1382 1383 private function password_hash_method() { 1384 $m = $this->conf->opt("passwordHashMethod"); 1385 return is_int($m) ? $m : PASSWORD_DEFAULT; 1386 } 1387 1388 private function check_password_encryption($hash, $iscdb) { 1389 $safe = $this->conf->opt($iscdb ? "contactdb_safePasswords" : "safePasswords"); 1390 if ($safe < 1 1391 || ($method = $this->password_hash_method()) === false 1392 || ($hash !== "" && $hash[0] !== " " && $safe == 1)) 1393 return false; 1394 else if ($hash === "" || $hash[0] !== " ") 1395 return true; 1396 else 1397 return $hash[1] !== "\$" 1398 || password_needs_rehash(substr($hash, 2), $method); 1399 } 1400 1401 function hash_password($input) { 1402 if (($method = $this->password_hash_method()) !== false) 1403 return " \$" . password_hash($input, $method); 1404 else 1405 return $input; 1406 } 1407 1408 function check_password($input) { 1409 global $Now; 1410 assert(!$this->conf->external_login()); 1411 if (($this->contactId && $this->disabled) 1412 || !self::valid_password($input)) 1413 return false; 1414 1415 $cdbu = $this->contactdb_user(); 1416 $cdbok = false; 1417 if ($cdbu 1418 && ($hash = $cdbu->password) 1419 && $cdbu->allow_contactdb_password() 1420 && ($cdbok = $this->check_hashed_password($input, $hash))) { 1421 $updater = ["passwordUseTime" => $Now]; 1422 if ($this->check_password_encryption($hash, true)) { 1423 $updater["password"] = $this->hash_password($input); 1424 $updater["passwordTime"] = $Now; 1425 } 1426 $cdbu->apply_updater($updater, true); 1427 } 1428 1429 $localok = false; 1430 if ($this->contactId 1431 && ($hash = $this->password) 1432 && ($localok = $this->check_hashed_password($input, $hash))) { 1433 if ($cdbu 1434 && !$cdbok 1435 && $this->passwordTime 1436 && $cdbu->passwordTime > $this->passwordTime) 1437 error_log($this->conf->dbname . ": " . $this->email . ": using old local password (" . post_value(true) . ")"); 1438 $updater = ["passwordUseTime" => $Now]; 1439 if ($this->check_password_encryption($hash, false)) { 1440 $updater["password"] = $cdbok ? $cdbu->password : $this->hash_password($input); 1441 $updater["passwordTime"] = $Now; 1442 } 1443 $this->apply_updater($updater, false); 1444 } 1445 1446 return $cdbok || $localok; 1447 } 1448 1449 const CHANGE_PASSWORD_PLAINTEXT = 1; 1450 const CHANGE_PASSWORD_ENABLE = 2; 1451 function change_password($new, $flags) { 1452 global $Now; 1453 assert(!$this->conf->external_login()); 1454 1455 $cdbu = $this->contactdb_user(); 1456 if (($flags & self::CHANGE_PASSWORD_ENABLE) 1457 && ($this->password !== "" || ($cdbu && (string) $cdbu->password !== ""))) 1458 return false; 1459 1460 if ($new === null) { 1461 $new = self::random_password(); 1462 $flags |= self::CHANGE_PASSWORD_PLAINTEXT; 1463 } 1464 assert(self::valid_password($new)); 1465 1466 if ($cdbu) { 1467 $hash = $new; 1468 if ($hash 1469 && !($flags & self::CHANGE_PASSWORD_PLAINTEXT) 1470 && $this->check_password_encryption("", true)) 1471 $hash = $this->hash_password($hash); 1472 $cdbu->password = $hash; 1473 $cdbu->passwordTime = $Now; 1474 Dbl::ql($this->conf->contactdb(), "update ContactInfo set password=?, passwordTime=? where contactDbId=?", $cdbu->password, $cdbu->passwordTime, $cdbu->contactDbId); 1475 if ($this->contactId && $this->password) { 1476 $this->password = ""; 1477 $this->passwordTime = $cdbu->passwordTime; 1478 $this->conf->ql("update ContactInfo set password=?, passwordTime=? where contactId=?", $this->password, $this->passwordTime, $this->contactId); 1479 } 1480 } else if ($this->contactId) { 1481 $hash = $new; 1482 if ($hash 1483 && !($flags & self::CHANGE_PASSWORD_PLAINTEXT) 1484 && $this->check_password_encryption("", false)) 1485 $hash = $this->hash_password($hash); 1486 $this->password = $hash; 1487 $this->passwordTime = $Now; 1488 $this->conf->ql("update ContactInfo set password=?, passwordTime=? where contactId=?", $this->password, $this->passwordTime, $this->contactId); 1489 } 1490 return true; 1491 } 1492 1493 1494 function sendAccountInfo($sendtype, $sensitive) { 1495 assert(!$this->disabled); 1496 1497 $cdbu = $this->contactdb_user(); 1498 $rest = array(); 1499 if ($sendtype === "create") { 1500 if ($cdbu && $cdbu->passwordUseTime) 1501 $template = "@activateaccount"; 1502 else 1503 $template = "@createaccount"; 1504 } else if ($sendtype === "forgot") { 1505 if ($this->conf->opt("safePasswords") <= 1 && $this->plaintext_password()) 1506 $template = "@accountinfo"; 1507 else { 1508 $capmgr = $this->conf->capability_manager($cdbu ? "U" : null); 1509 $rest["capability"] = $capmgr->create(CAPTYPE_RESETPASSWORD, array("user" => $this, "timeExpires" => time() + 259200)); 1510 $this->conf->log_for($this, null, "Created password reset " . substr($rest["capability"], 0, 8) . "..."); 1511 $template = "@resetpassword"; 1512 } 1513 } else { 1514 if ($this->plaintext_password()) 1515 $template = "@accountinfo"; 1516 else 1517 return false; 1518 } 1519 1520 $mailer = new HotCRPMailer($this->conf, $this, null, $rest); 1521 $prep = $mailer->make_preparation($template, $rest); 1522 if ($prep->sendable 1523 || !$sensitive 1524 || $this->conf->opt("debugShowSensitiveEmail")) { 1525 $prep->send(); 1526 return $template; 1527 } else { 1528 Conf::msg_error("Mail cannot be sent to " . htmlspecialchars($this->email) . " at this time."); 1529 return false; 1530 } 1531 } 1532 1533 1534 function mark_login() { 1535 global $Now; 1536 // at least one login every 30 days is marked as activity 1537 if ((int) $this->activity_at <= $Now - 2592000 1538 || (($cdbu = $this->contactdb_user()) 1539 && ((int) $cdbu->activity_at <= $Now - 2592000))) 1540 $this->mark_activity(); 1541 } 1542 1543 function mark_activity() { 1544 global $Now; 1545 if ((!$this->activity_at || $this->activity_at < $Now) 1546 && !$this->is_anonymous_user()) { 1547 $this->activity_at = $Now; 1548 if ($this->contactId) 1549 $this->conf->ql("update ContactInfo set lastLogin=$Now where contactId=$this->contactId"); 1550 if (($cdbu = $this->contactdb_user()) 1551 && $cdbu->confid 1552 && (int) $cdbu->activity_at <= $Now - 604800) 1553 $this->_contactdb_save_roles($cdbu); 1554 } 1555 } 1556 1557 function log_activity($text, $paperId = null) { 1558 $this->mark_activity(); 1559 if (!$this->is_anonymous_user()) 1560 $this->conf->log_for($this, $this, $text, $paperId); 1561 } 1562 1563 function log_activity_for($user, $text, $paperId = null) { 1564 $this->mark_activity(); 1565 if (!$this->is_anonymous_user()) 1566 $this->conf->log_for($this, $user, $text, $paperId); 1567 } 1568 1569 1570 // HotCRP roles 1571 1572 static function update_rights() { 1573 ++self::$rights_version; 1574 } 1575 1576 private function load_author_reviewer_status() { 1577 // Load from database 1578 $result = null; 1579 if ($this->contactId > 0) { 1580 $qs = ["exists (select * from PaperConflict where contactId=? and conflictType>=" . CONFLICT_AUTHOR . ")", 1581 "exists (select * from PaperReview where contactId=?)"]; 1582 $qv = [$this->contactId, $this->contactId]; 1583 if ($this->isPC) { 1584 $qs[] = "exists (select * from PaperReview where requestedBy=? and contactId!=?)"; 1585 array_push($qv, $this->contactId, $this->contactId); 1586 } else 1587 $qs[] = "0"; 1588 if ($this->_review_tokens) { 1589 $qs[] = "exists (select * from PaperReview where reviewToken?a)"; 1590 $qv[] = $this->_review_tokens; 1591 } else 1592 $qs[] = "0"; 1593 $result = $this->conf->qe_apply("select " . join(", ", $qs), $qv); 1594 } 1595 $row = $result ? $result->fetch_row() : null; 1596 $this->_db_roles = ($row && $row[0] > 0 ? self::ROLE_AUTHOR : 0) 1597 | ($row && $row[1] > 0 ? self::ROLE_REVIEWER : 0) 1598 | ($row && $row[2] > 0 ? self::ROLE_REQUESTER : 0); 1599 $this->_active_roles = $this->_db_roles 1600 | ($row && $row[3] > 0 ? self::ROLE_REVIEWER : 0); 1601 Dbl::free($result); 1602 1603 // Update contact information from capabilities 1604 if ($this->capabilities) { 1605 foreach ($this->capabilities as $pid => $cap) 1606 if ($pid && ($cap & self::CAP_AUTHORVIEW)) 1607 $this->_active_roles |= self::ROLE_AUTHOR; 1608 } 1609 } 1610 1611 private function check_rights_version() { 1612 if ($this->_rights_version !== self::$rights_version) { 1613 $this->_db_roles = $this->_active_roles = 1614 $this->_has_outstanding_review = $this->_is_lead = 1615 $this->_is_explicit_manager = $this->_is_metareviewer = 1616 $this->_can_view_pc = $this->_dangerous_track_mask = 1617 $this->_authored_papers = null; 1618 $this->_rights_version = self::$rights_version; 1619 } 1620 } 1621 1622 function is_author() { 1623 $this->check_rights_version(); 1624 if (!isset($this->_active_roles)) 1625 $this->load_author_reviewer_status(); 1626 return ($this->_active_roles & self::ROLE_AUTHOR) !== 0; 1627 } 1628 1629 function authored_papers() { 1630 $this->check_rights_version(); 1631 if ($this->_authored_papers === null) 1632 $this->_authored_papers = $this->is_author() ? $this->conf->paper_set($this, ["author" => true, "tags" => true])->all() : []; 1633 return $this->_authored_papers; 1634 } 1635 1636 function has_review() { 1637 $this->check_rights_version(); 1638 if (!isset($this->_active_roles)) 1639 $this->load_author_reviewer_status(); 1640 return ($this->_active_roles & self::ROLE_REVIEWER) !== 0; 1641 } 1642 1643 function is_reviewer() { 1644 return $this->isPC || $this->has_review(); 1645 } 1646 1647 function is_metareviewer() { 1648 if (!isset($this->_is_metareviewer)) { 1649 if ($this->isPC && $this->conf->setting("metareviews")) 1650 $this->_is_metareviewer = !!$this->conf->fetch_ivalue("select exists (select * from PaperReview where contactId={$this->contactId} and reviewType=" . REVIEW_META . ")"); 1651 else 1652 $this->_is_metareviewer = false; 1653 } 1654 return $this->_is_metareviewer; 1655 } 1656 1657 function contactdb_roles() { 1658 $this->is_author(); // load _db_roles 1659 return $this->roles | ($this->_db_roles & (self::ROLE_AUTHOR | self::ROLE_REVIEWER)); 1660 } 1661 1662 function has_outstanding_review() { 1663 $this->check_rights_version(); 1664 if ($this->_has_outstanding_review === null) { 1665 $this->_has_outstanding_review = $this->has_review() 1666 && $this->conf->fetch_ivalue("select exists (select * from PaperReview join Paper using (paperId) where Paper.timeSubmitted>0 and " . $this->act_reviewer_sql("PaperReview") . " and reviewNeedsSubmit!=0)"); 1667 } 1668 return $this->_has_outstanding_review; 1669 } 1670 1671 function is_requester() { 1672 $this->check_rights_version(); 1673 if (!isset($this->_active_roles)) 1674 $this->load_author_reviewer_status(); 1675 return ($this->_active_roles & self::ROLE_REQUESTER) !== 0; 1676 } 1677 1678 function is_discussion_lead() { 1679 $this->check_rights_version(); 1680 if (!isset($this->_is_lead)) { 1681 $result = null; 1682 if ($this->contactId > 0) 1683 $result = $this->conf->qe("select exists (select * from Paper where leadContactId=?)", $this->contactId); 1684 $this->_is_lead = edb_nrows($result) > 0; 1685 Dbl::free($result); 1686 } 1687 return $this->_is_lead; 1688 } 1689 1690 function is_explicit_manager() { 1691 $this->check_rights_version(); 1692 if (!isset($this->_is_explicit_manager)) { 1693 $this->_is_explicit_manager = false; 1694 if ($this->contactId > 0 1695 && $this->isPC 1696 && ($this->conf->check_any_admin_tracks($this) 1697 || ($this->conf->has_any_manager() 1698 && $this->conf->fetch_value("select exists (select * from Paper where managerContactId=?)", $this->contactId) > 0))) 1699 $this->_is_explicit_manager = true; 1700 } 1701 return $this->_is_explicit_manager; 1702 } 1703 1704 function is_manager() { 1705 return $this->privChair || $this->is_explicit_manager(); 1706 } 1707 1708 function is_track_manager() { 1709 return $this->privChair || $this->conf->check_any_admin_tracks($this); 1710 } 1711 1712 1713 // review tokens 1714 1715 function review_tokens() { 1716 return $this->_review_tokens ? : []; 1717 } 1718 1719 function active_review_token_for(PaperInfo $prow, ReviewInfo $rrow = null) { 1720 if ($this->_review_tokens) { 1721 if ($rrow) { 1722 if ($rrow->reviewToken && in_array($rrow->reviewToken, $this->_review_tokens)) 1723 return (int) $rrow->reviewToken; 1724 } else { 1725 foreach ($prow->reviews_by_id() as $rrow) 1726 if ($rrow->reviewToken && in_array($rrow->reviewToken, $this->_review_tokens)) 1727 return (int) $rrow->reviewToken; 1728 } 1729 } 1730 return false; 1731 } 1732 1733 function change_review_token($token, $on) { 1734 assert($token !== false || $on === false); 1735 if (!$this->_review_tokens) 1736 $this->_review_tokens = array(); 1737 $old_ntokens = count($this->_review_tokens); 1738 if (!$on && $token === false) 1739 $this->_review_tokens = array(); 1740 else { 1741 $pos = array_search($token, $this->_review_tokens); 1742 if (!$on && $pos !== false) 1743 array_splice($this->_review_tokens, $pos, 1); 1744 else if ($on && $pos === false && $token != 0) 1745 $this->_review_tokens[] = $token; 1746 } 1747 $new_ntokens = count($this->_review_tokens); 1748 if ($new_ntokens == 0) 1749 $this->_review_tokens = null; 1750 if ($new_ntokens != $old_ntokens) 1751 self::update_rights(); 1752 if ($this->_activated && $new_ntokens != $old_ntokens) 1753 $this->conf->save_session("rev_tokens", $this->_review_tokens); 1754 return $new_ntokens != $old_ntokens; 1755 } 1756 1757 1758 // topic interests 1759 1760 function topic_interest_map() { 1761 global $Me; 1762 if ($this->_topic_interest_map !== null) 1763 return $this->_topic_interest_map; 1764 if ($this->contactId <= 0 || !$this->conf->has_topics()) 1765 return array(); 1766 if (($this->roles & self::ROLE_PCLIKE) 1767 && $this !== $Me 1768 && ($pcm = $this->conf->pc_members()) 1769 && $this === get($pcm, $this->contactId)) 1770 self::load_topic_interests($pcm); 1771 else { 1772 $result = $this->conf->qe("select topicId, interest from TopicInterest where contactId={$this->contactId} and interest!=0"); 1773 $this->_topic_interest_map = Dbl::fetch_iimap($result); 1774 } 1775 return $this->_topic_interest_map; 1776 } 1777 1778 static function load_topic_interests($contacts) { 1779 if (empty($contacts)) 1780 return; 1781 $cbyid = []; 1782 foreach ($contacts as $c) { 1783 $c->_topic_interest_map = []; 1784 $cbyid[$c->contactId] = $c; 1785 } 1786 $result = $c->conf->qe("select contactId, topicId, interest from TopicInterest where interest!=0 order by contactId"); 1787 $c = null; 1788 while (($row = edb_row($result))) { 1789 if (!$c || $c->contactId != $row[0]) 1790 $c = get($cbyid, $row[0]); 1791 if ($c) 1792 $c->_topic_interest_map[(int) $row[1]] = (int) $row[2]; 1793 } 1794 Dbl::free($result); 1795 } 1796 1797 1798 // permissions policies 1799 1800 private function rights(PaperInfo $prow, $forceShow = null) { 1801 $ci = $prow->contact_info($this); 1802 1803 // check first whether administration is allowed 1804 if (!isset($ci->allow_administer)) { 1805 $ci->allow_administer = false; 1806 if (($this->contactId > 0 1807 && (!$prow->managerContactId 1808 || $prow->managerContactId == $this->contactId 1809 || !$ci->conflictType) 1810 && ($this->privChair 1811 || $prow->managerContactId == $this->contactId 1812 || ($this->isPC 1813 && $this->is_track_manager() 1814 && $this->conf->check_admin_tracks($prow, $this)))) 1815 || $this->is_site_contact) { 1816 $ci->allow_administer = true; 1817 } 1818 } 1819 1820 // correct $forceShow 1821 if (!$ci->allow_administer) 1822 $forceShow = false; 1823 else if ($forceShow === null) 1824 $forceShow = ($this->_overrides & self::OVERRIDE_CONFLICT) !== 0; 1825 else if ($forceShow === "any") 1826 $forceShow = !!$ci->forced_rights_link; 1827 if ($forceShow) 1828 $ci = $ci->get_forced_rights(); 1829 1830 // set other rights 1831 if ($ci->rights_forced !== $forceShow) { 1832 $ci->rights_forced = $forceShow; 1833 1834 // check current administration status 1835 $ci->can_administer = $ci->allow_administer 1836 && (!$ci->conflictType || $forceShow); 1837 1838 // check PC tracking 1839 // (see also can_accept_review_assignment*) 1840 $tracks = $this->conf->has_tracks(); 1841 $am_lead = $this->contactId > 0 && isset($prow->leadContactId) 1842 && $prow->leadContactId == $this->contactId; 1843 $isPC = $this->isPC 1844 && (!$tracks 1845 || $ci->reviewType >= REVIEW_PC 1846 || $am_lead 1847 || !$this->conf->check_track_view_sensitivity() 1848 || $this->conf->check_tracks($prow, $this, Track::VIEW)); 1849 1850 // check whether PC privileges apply 1851 $ci->allow_pc_broad = $ci->allow_administer || $isPC; 1852 $ci->allow_pc = $ci->can_administer 1853 || ($isPC && !$ci->conflictType); 1854 1855 // check whether this is a potential reviewer 1856 // (existing external reviewer or PC) 1857 if ($ci->reviewType > 0 || $am_lead || $ci->allow_administer) 1858 $ci->potential_reviewer = true; 1859 else if ($ci->allow_pc) 1860 $ci->potential_reviewer = !$tracks 1861 || $this->conf->check_tracks($prow, $this, Track::UNASSREV); 1862 else 1863 $ci->potential_reviewer = false; 1864 $ci->allow_review = $ci->potential_reviewer 1865 && ($ci->can_administer || !$ci->conflictType); 1866 1867 // check author allowance 1868 $ci->act_author = $ci->conflictType >= CONFLICT_AUTHOR; 1869 $ci->allow_author = $ci->act_author || $ci->allow_administer; 1870 1871 // check author view allowance (includes capabilities) 1872 // If an author-view capability is set, then use it -- unless 1873 // this user is a PC member or reviewer, which takes priority. 1874 $ci->view_conflict_type = $ci->conflictType; 1875 if (isset($this->capabilities) 1876 && isset($this->capabilities[$prow->paperId]) 1877 && ($this->capabilities[$prow->paperId] & self::CAP_AUTHORVIEW) 1878 && !$isPC 1879 && !$ci->review_status) 1880 $ci->view_conflict_type = CONFLICT_AUTHOR; 1881 $ci->act_author_view = $ci->view_conflict_type >= CONFLICT_AUTHOR; 1882 $ci->allow_author_view = $ci->act_author_view || $ci->allow_administer; 1883 1884 // check blindness 1885 $bs = $this->conf->submission_blindness(); 1886 $ci->nonblind = $bs == Conf::BLIND_NEVER 1887 || ($bs == Conf::BLIND_OPTIONAL 1888 && !$prow->blind) 1889 || ($bs == Conf::BLIND_UNTILREVIEW 1890 && $ci->review_status > 0) 1891 || ($prow->outcome > 0 1892 && ($isPC || $ci->allow_review) 1893 && $this->conf->time_reviewer_view_accepted_authors()); 1894 1895 // check dangerous track mask 1896 if ($ci->allow_administer && $this->_dangerous_track_mask === null) 1897 $this->_dangerous_track_mask = $this->conf->dangerous_track_mask($this); 1898 } 1899 1900 return $ci; 1901 } 1902 1903 function __rights(PaperInfo $prow, $forceShow = null) { 1904 // public access point; to be avoided 1905 return $this->rights($prow, $forceShow); 1906 } 1907 1908 function override_deadlines($rights) { 1909 if (!($this->_overrides & self::OVERRIDE_TIME)) 1910 return false; 1911 if ($rights && $rights instanceof PaperInfo) 1912 $rights = $this->rights($rights); 1913 return $rights ? $rights->allow_administer : $this->privChair; 1914 } 1915 1916 function allow_administer(PaperInfo $prow = null) { 1917 if ($prow) { 1918 $rights = $this->rights($prow); 1919 return $rights->allow_administer; 1920 } else 1921 return $this->privChair; 1922 } 1923 1924 function can_meaningfully_override(PaperInfo $prow) { 1925 if ($this->is_manager()) { 1926 $rights = $this->rights($prow, "any"); 1927 return $rights->allow_administer 1928 && ($rights->conflictType > 0 || $this->_dangerous_track_mask); 1929 } else 1930 return false; 1931 } 1932 1933 function can_change_password($acct) { 1934 if ($this->privChair 1935 && !$this->conf->opt("chairHidePasswords")) 1936 return true; 1937 else 1938 return $acct 1939 && $this->contactId > 0 1940 && $this->contactId == $acct->contactId 1941 && isset($_SESSION) 1942 && isset($_SESSION["trueuser"]) 1943 && strcasecmp($_SESSION["trueuser"]->email, $acct->email) == 0; 1944 } 1945 1946 function can_administer(PaperInfo $prow = null, $forceShow = null) { 1947 if ($prow) { 1948 $rights = $this->rights($prow, $forceShow); 1949 return $rights->can_administer; 1950 } else 1951 return $this->privChair; 1952 } 1953 1954 private function _can_administer_for_track(PaperInfo $prow, $rights, $ttype) { 1955 return $rights->can_administer 1956 && (!($this->_dangerous_track_mask & (1 << $ttype)) 1957 || $this->conf->check_tracks($prow, $this, $ttype) 1958 || ($this->_overrides & self::OVERRIDE_CONFLICT) !== 0); 1959 } 1960 1961 function can_administer_for_track(PaperInfo $prow = null, $ttype) { 1962 if ($prow) 1963 return $this->_can_administer_for_track($prow, $this->rights($prow), $ttype); 1964 else 1965 return $this->privChair; 1966 } 1967 1968 function act_pc(PaperInfo $prow = null, $forceShow = null) { 1969 if ($prow) { 1970 $rights = $this->rights($prow, $forceShow); 1971 return $rights->allow_pc; 1972 } else 1973 return $this->isPC; 1974 } 1975 1976 function can_view_pc() { 1977 $this->check_rights_version(); 1978 if ($this->_can_view_pc === null) { 1979 if ($this->is_manager()) 1980 $this->_can_view_pc = 2; 1981 else if ($this->isPC) 1982 $this->_can_view_pc = $this->conf->opt("secretPC") ? 0 : 2; 1983 else 1984 $this->_can_view_pc = $this->conf->opt("privatePC") ? 0 : 1; 1985 } 1986 return $this->_can_view_pc > 0; 1987 } 1988 function can_view_contact_tags() { 1989 return $this->privChair 1990 || ($this->can_view_pc() && $this->_can_view_pc > 1); 1991 } 1992 1993 function can_view_tracker() { 1994 return $this->privChair 1995 || ($this->isPC && $this->conf->check_default_track($this, Track::VIEWTRACKER)) 1996 || $this->tracker_kiosk_state; 1997 } 1998 1999 function view_conflict_type(PaperInfo $prow = null) { 2000 if ($prow) { 2001 $rights = $this->rights($prow); 2002 return $rights->view_conflict_type; 2003 } else 2004 return 0; 2005 } 2006 2007 function act_author_view(PaperInfo $prow) { 2008 $rights = $this->rights($prow); 2009 return $rights->act_author_view; 2010 } 2011 2012 function act_author_view_sql($table, $only_if_complex = false) { 2013 $m = []; 2014 if (isset($this->capabilities) && !$this->isPC) { 2015 foreach ($this->capabilities as $pid => $cap) 2016 if ($pid && ($cap & Contact::CAP_AUTHORVIEW)) 2017 $m[] = "Paper.paperId=$pid"; 2018 } 2019 if (empty($m) && $this->contactId && $only_if_complex) 2020 return false; 2021 if ($this->contactId) 2022 $m[] = "$table.conflictType>=" . CONFLICT_AUTHOR; 2023 if (count($m) > 1) 2024 return "(" . join(" or ", $m) . ")"; 2025 else 2026 return empty($m) ? "false" : $m[0]; 2027 } 2028 2029 function act_reviewer_sql($table) { 2030 $sql = $this->contactId ? "$table.contactId={$this->contactId}" : "false"; 2031 if (($rev_tokens = $this->review_tokens())) 2032 $sql = "($sql or $table.reviewToken in (" . join(",", $rev_tokens) . "))"; 2033 return $sql; 2034 } 2035 2036 function can_start_paper() { 2037 return $this->email 2038 && ($this->conf->timeStartPaper() 2039 || $this->override_deadlines(null)); 2040 } 2041 2042 function perm_start_paper() { 2043 if ($this->can_start_paper()) 2044 return null; 2045 return array("deadline" => "sub_reg", "override" => $this->privChair); 2046 } 2047 2048 function can_edit_paper(PaperInfo $prow) { 2049 $rights = $this->rights($prow, "any"); 2050 return $rights->allow_administer || $prow->has_author($this); 2051 } 2052 2053 function can_update_paper(PaperInfo $prow) { 2054 $rights = $this->rights($prow, "any"); 2055 return $rights->allow_author 2056 && $prow->timeWithdrawn <= 0 2057 && (($prow->outcome >= 0 && $this->conf->timeUpdatePaper($prow)) 2058 || $this->override_deadlines($rights)); 2059 } 2060 2061 function perm_update_paper(PaperInfo $prow) { 2062 if ($this->can_update_paper($prow)) 2063 return null; 2064 $rights = $this->rights($prow, "any"); 2065 $whyNot = $prow->make_whynot(); 2066 if (!$rights->allow_author && $rights->allow_author_view) 2067 $whyNot["signin"] = "edit_paper"; 2068 else if (!$rights->allow_author) 2069 $whyNot["author"] = 1; 2070 if ($prow->timeWithdrawn > 0) 2071 $whyNot["withdrawn"] = 1; 2072 if ($prow->outcome < 0 && $this->can_view_decision($prow)) 2073 $whyNot["rejected"] = 1; 2074 if ($prow->timeSubmitted > 0 && $this->conf->setting("sub_freeze") > 0) 2075 $whyNot["updateSubmitted"] = 1; 2076 if (!$this->conf->timeUpdatePaper($prow) && !$this->override_deadlines($rights)) 2077 $whyNot["deadline"] = "sub_update"; 2078 if ($rights->allow_administer) 2079 $whyNot["override"] = 1; 2080 return $whyNot; 2081 } 2082 2083 function can_finalize_paper(PaperInfo $prow) { 2084 $rights = $this->rights($prow, "any"); 2085 return $rights->allow_author 2086 && $prow->timeWithdrawn <= 0 2087 && ($this->conf->timeFinalizePaper($prow) || $this->override_deadlines($rights)); 2088 } 2089 2090 function perm_finalize_paper(PaperInfo $prow) { 2091 if ($this->can_finalize_paper($prow)) 2092 return null; 2093 $rights = $this->rights($prow, "any"); 2094 $whyNot = $prow->make_whynot(); 2095 if (!$rights->allow_author && $rights->allow_author_view) 2096 $whyNot["signin"] = "edit_paper"; 2097 else if (!$rights->allow_author) 2098 $whyNot["author"] = 1; 2099 if ($prow->timeWithdrawn > 0) 2100 $whyNot["withdrawn"] = 1; 2101 if ($prow->timeSubmitted > 0) 2102 $whyNot["updateSubmitted"] = 1; 2103 if (!$this->conf->timeFinalizePaper($prow) && !$this->override_deadlines($rights)) 2104 $whyNot["deadline"] = "sub_sub"; 2105 if ($rights->allow_administer) 2106 $whyNot["override"] = 1; 2107 return $whyNot; 2108 } 2109 2110 function can_withdraw_paper(PaperInfo $prow) { 2111 $rights = $this->rights($prow, "any"); 2112 return $rights->allow_author 2113 && $prow->timeWithdrawn <= 0 2114 && ($prow->outcome == 0 || $this->override_deadlines($rights)); 2115 } 2116 2117 function perm_withdraw_paper(PaperInfo $prow) { 2118 if ($this->can_withdraw_paper($prow)) 2119 return null; 2120 $rights = $this->rights($prow, "any"); 2121 $whyNot = $prow->make_whynot(); 2122 if ($prow->timeWithdrawn > 0) 2123 $whyNot["withdrawn"] = 1; 2124 if (!$rights->allow_author && $rights->allow_author_view) 2125 $whyNot["signin"] = "edit_paper"; 2126 else if (!$rights->allow_author) 2127 $whyNot["author"] = 1; 2128 else if ($prow->outcome != 0 && !$this->override_deadlines($rights)) 2129 $whyNot["decided"] = 1; 2130 if ($rights->allow_administer) 2131 $whyNot["override"] = 1; 2132 return $whyNot; 2133 } 2134 2135 function can_revive_paper(PaperInfo $prow) { 2136 $rights = $this->rights($prow, "any"); 2137 return $rights->allow_author 2138 && $prow->timeWithdrawn > 0 2139 && ($this->conf->timeUpdatePaper($prow) || $this->override_deadlines($rights)); 2140 } 2141 2142 function perm_revive_paper(PaperInfo $prow) { 2143 if ($this->can_revive_paper($prow)) 2144 return null; 2145 $rights = $this->rights($prow, "any"); 2146 $whyNot = $prow->make_whynot(); 2147 if (!$rights->allow_author && $rights->allow_author_view) 2148 $whyNot["signin"] = "edit_paper"; 2149 else if (!$rights->allow_author) 2150 $whyNot["author"] = 1; 2151 if ($prow->timeWithdrawn <= 0) 2152 $whyNot["notWithdrawn"] = 1; 2153 if (!$this->conf->timeUpdatePaper($prow) && !$this->override_deadlines($rights)) 2154 $whyNot["deadline"] = "sub_update"; 2155 if ($rights->allow_administer) 2156 $whyNot["override"] = 1; 2157 return $whyNot; 2158 } 2159 2160 function can_submit_final_paper(PaperInfo $prow) { 2161 // see also EditFinal_SearchTerm 2162 $rights = $this->rights($prow, "any"); 2163 return $rights->allow_author 2164 && $prow->timeWithdrawn <= 0 2165 && $prow->outcome > 0 2166 && $this->conf->collectFinalPapers() 2167 && $this->can_view_decision($prow) 2168 && ($this->conf->time_submit_final_version() 2169 || $this->override_deadlines($rights)); 2170 } 2171 2172 function perm_submit_final_paper(PaperInfo $prow) { 2173 if ($this->can_submit_final_paper($prow)) 2174 return null; 2175 $rights = $this->rights($prow, "any"); 2176 $whyNot = $prow->make_whynot(); 2177 if (!$rights->allow_author && $rights->allow_author_view) 2178 $whyNot["signin"] = "edit_paper"; 2179 else if (!$rights->allow_author) 2180 $whyNot["author"] = 1; 2181 if ($prow->timeWithdrawn > 0) 2182 $whyNot["withdrawn"] = 1; 2183 // NB logic order here is important elsewhere 2184 // Don’t report “rejected” error to admins 2185 if ($prow->outcome <= 0 2186 || (!$rights->allow_administer 2187 && !$this->can_view_decision($prow))) 2188 $whyNot["rejected"] = 1; 2189 else if (!$this->conf->collectFinalPapers()) 2190 $whyNot["deadline"] = "final_open"; 2191 else if (!$this->conf->time_submit_final_version() 2192 && !$this->override_deadlines($rights)) 2193 $whyNot["deadline"] = "final_done"; 2194 if ($rights->allow_administer) 2195 $whyNot["override"] = 1; 2196 return $whyNot; 2197 } 2198 2199 function has_hidden_papers() { 2200 return $this->hidden_papers !== null; 2201 } 2202 2203 function can_view_paper(PaperInfo $prow, $pdf = false) { 2204 // hidden_papers is set when a chair with a conflicted, managed 2205 // paper “becomes” a user 2206 if ($this->hidden_papers !== null 2207 && isset($this->hidden_papers[$prow->paperId])) { 2208 $this->hidden_papers[$prow->paperId] = true; 2209 return false; 2210 } 2211 if ($this->privChair) 2212 return true; 2213 $rights = $this->rights($prow, "any"); 2214 return $rights->allow_author_view 2215 || ($rights->review_status != 0 2216 // assigned reviewer can view PDF of withdrawn, but submitted, paper 2217 && (!$pdf || $prow->timeSubmitted != 0)) 2218 || ($rights->allow_pc_broad 2219 && $this->conf->timePCViewPaper($prow, $pdf) 2220 && (!$pdf || $this->conf->check_tracks($prow, $this, Track::VIEWPDF))); 2221 } 2222 2223 function perm_view_paper(PaperInfo $prow, $pdf = false) { 2224 if ($this->can_view_paper($prow, $pdf)) 2225 return null; 2226 $rights = $this->rights($prow, "any"); 2227 $whyNot = $prow->make_whynot(); 2228 $base_count = count($whyNot); 2229 if (!$rights->allow_author_view 2230 && !$rights->review_status 2231 && !$rights->allow_pc_broad) 2232 $whyNot["permission"] = "view_paper"; 2233 else { 2234 if ($prow->timeWithdrawn > 0) 2235 $whyNot["withdrawn"] = 1; 2236 else if ($prow->timeSubmitted <= 0) 2237 $whyNot["notSubmitted"] = 1; 2238 if ($rights->allow_pc_broad 2239 && !$this->conf->timePCViewPaper($prow, false)) 2240 $whyNot["deadline"] = "sub_sub"; 2241 if ($pdf 2242 && count($whyNot) == $base_count 2243 && $this->can_view_paper($prow)) 2244 $whyNot["pdfPermission"] = 1; 2245 } 2246 return $whyNot; 2247 } 2248 2249 function can_view_pdf(PaperInfo $prow) { 2250 return $this->can_view_paper($prow, true); 2251 } 2252 2253 function perm_view_pdf(PaperInfo $prow) { 2254 return $this->perm_view_paper($prow, true); 2255 } 2256 2257 function can_view_some_pdf() { 2258 return $this->privChair 2259 || $this->is_author() 2260 || $this->has_review() 2261 || ($this->isPC && $this->conf->has_any_pc_visible_pdf()); 2262 } 2263 2264 function can_view_document_history(PaperInfo $prow) { 2265 if ($this->privChair) 2266 return true; 2267 $rights = $this->rights($prow, "any"); 2268 return $rights->act_author || $rights->can_administer; 2269 } 2270 2271 function can_view_manager(PaperInfo $prow = null) { 2272 if ($this->privChair) 2273 return true; 2274 if (!$prow) 2275 return (!$this->conf->opt("hideManager") && $this->is_reviewer()) 2276 || ($this->isPC && $this->is_explicit_manager()); 2277 $rights = $this->rights($prow, "any"); 2278 return $prow->managerContactId == $this->contactId 2279 || ($rights->potential_reviewer && !$this->conf->opt("hideManager")); 2280 } 2281 2282 function can_view_lead(PaperInfo $prow = null) { 2283 if ($prow) { 2284 $rights = $this->rights($prow); 2285 return $rights->can_administer 2286 || ($this->contactId > 0 2287 && isset($prow->leadContactId) 2288 && $prow->leadContactId == $this->contactId) 2289 || (($rights->allow_pc || $rights->allow_review) 2290 && $this->can_view_review_identity($prow, null)); 2291 } else 2292 return $this->isPC; 2293 } 2294 2295 function can_view_shepherd(PaperInfo $prow = null) { 2296 // XXX Allow shepherd view when outcome == 0 && can_view_decision. 2297 // This is a mediocre choice, but people like to reuse the shepherd field 2298 // for other purposes, and I might hear complaints. 2299 if ($prow) { 2300 return $this->act_pc($prow) 2301 || (!$this->conf->setting("shepherd_hide") 2302 && $this->can_view_decision($prow) 2303 && $this->can_view_review($prow, null)); 2304 } else { 2305 return $this->isPC 2306 || (!$this->conf->setting("shepherd_hide") 2307 && $this->can_view_some_decision_as_author()); 2308 } 2309 } 2310 2311 /* NB caller must check can_view_paper() */ 2312 function can_view_authors(PaperInfo $prow, $forceShow = null) { 2313 $rights = $this->rights($prow, $forceShow); 2314 return ($rights->nonblind 2315 && $prow->timeSubmitted != 0 2316 && ($rights->allow_pc_broad 2317 || $rights->review_status != 0)) 2318 || ($rights->nonblind 2319 && $prow->timeWithdrawn <= 0 2320 && $rights->allow_pc_broad 2321 && $this->conf->can_pc_see_all_submissions()) 2322 || ($rights->allow_administer 2323 ? $rights->nonblind || $rights->rights_forced /* chair can't see blind authors unless forceShow */ 2324 : $rights->act_author_view); 2325 } 2326 2327 function allow_view_authors(PaperInfo $prow) { 2328 $rights = $this->rights($prow); 2329 return $rights->allow_administer 2330 || $rights->act_author_view 2331 || ($rights->nonblind 2332 && $prow->timeSubmitted != 0 2333 && ($rights->allow_pc_broad 2334 || $rights->review_status != 0)) 2335 || ($rights->nonblind 2336 && $prow->timeWithdrawn <= 0 2337 && $rights->allow_pc_broad 2338 && $this->conf->can_pc_see_all_submissions()); 2339 } 2340 2341 function can_view_some_authors() { 2342 return $this->is_manager() 2343 || $this->is_author() 2344 || ($this->is_reviewer() 2345 && ($this->conf->submission_blindness() != Conf::BLIND_ALWAYS 2346 || $this->conf->time_reviewer_view_accepted_authors())); 2347 } 2348 2349 function can_view_conflicts(PaperInfo $prow) { 2350 $rights = $this->rights($prow); 2351 if ($rights->allow_administer || $rights->act_author_view) 2352 return true; 2353 if (!$rights->allow_pc_broad && !$rights->potential_reviewer) 2354 return false; 2355 $pccv = $this->conf->setting("sub_pcconfvis"); 2356 return $pccv == 2 2357 || (!$pccv && $this->can_view_authors($prow)) 2358 || (!$pccv && $this->conf->setting("tracker") 2359 && MeetingTracker::is_paper_tracked($prow) 2360 && $this->can_view_tracker()); 2361 } 2362 2363 function can_view_paper_option(PaperInfo $prow, $opt) { 2364 if (!is_object($opt) 2365 && !($opt = $this->conf->paper_opts->get($opt))) 2366 return false; 2367 if (!$this->can_view_paper($prow, $opt->has_document())) 2368 return false; 2369 if ($opt->final 2370 && ($prow->outcome <= 0 2371 || !$this->can_view_decision($prow)) 2372 && ($opt->id === DTYPE_FINAL 2373 ? $prow->finalPaperStorageId <= 1 2374 : !$prow->option($opt->id))) 2375 return false; 2376 if ($opt->edit_condition() 2377 && !($this->_overrides & self::OVERRIDE_EDIT_CONDITIONS) 2378 && !$opt->test_edit_condition($prow)) 2379 return false; 2380 $rights = $this->rights($prow); 2381 $oview = $opt->visibility; 2382 if ($rights->allow_administer) 2383 return $oview !== "nonblind" || $this->can_view_authors($prow); 2384 else 2385 return $rights->act_author_view 2386 || (($rights->review_status != 0 2387 || $rights->allow_pc_broad) 2388 && (!$oview 2389 || $oview == "rev" 2390 || ($oview == "nonblind" 2391 && $this->can_view_authors($prow)))); 2392 } 2393 2394 function user_option_list() { 2395 if ($this->conf->has_any_accepted() && $this->can_view_some_decision()) 2396 return $this->conf->paper_opts->option_list(); 2397 else 2398 return $this->conf->paper_opts->nonfinal_option_list(); 2399 } 2400 2401 function perm_view_paper_option(PaperInfo $prow, $opt) { 2402 if ($this->can_view_paper_option($prow, $opt)) 2403 return null; 2404 if (!is_object($opt) && !($opt = $this->conf->paper_opts->get($opt))) 2405 return $prow->make_whynot(); 2406 if (($whyNot = $this->perm_view_paper($prow, $opt->has_document()))) 2407 return $whyNot; 2408 $whyNot = $prow->make_whynot(); 2409 $rights = $this->rights($prow); 2410 $oview = $opt->visibility; 2411 if ($rights->allow_administer 2412 ? $oview === "nonblind" 2413 && !$this->can_view_authors($prow) 2414 : !$rights->act_author_view 2415 && ($oview === "admin" 2416 || ((!$oview || $oview == "rev") 2417 && !$rights->review_status 2418 && !$rights->allow_pc_broad) 2419 || ($oview == "nonblind" 2420 && !$this->can_view_authors($prow)))) 2421 $whyNot["optionPermission"] = $opt; 2422 else if ($opt->final && ($prow->outcome <= 0 || !$this->can_view_decision($prow))) 2423 $whyNot["optionNotAccepted"] = $opt; 2424 else 2425 $whyNot["optionPermission"] = $opt; 2426 return $whyNot; 2427 } 2428 2429 function can_view_some_paper_option(PaperOption $opt) { 2430 if (($opt->has_document() && !$this->can_view_some_pdf()) 2431 || ($opt->final && !$this->can_view_some_decision())) 2432 return false; 2433 $oview = $opt->visibility; 2434 return $this->is_author() 2435 || ($oview == "admin" && $this->is_manager()) 2436 || ((!$oview || $oview == "rev") && $this->is_reviewer()) 2437 || ($oview == "nonblind" && $this->can_view_some_authors()); 2438 } 2439 2440 function is_my_review(ReviewInfo $rrow = null) { 2441 return $rrow 2442 && ($rrow->contactId == $this->contactId 2443 || ($this->_review_tokens 2444 && $rrow->reviewToken 2445 && in_array($rrow->reviewToken, $this->_review_tokens))); 2446 } 2447 2448 function is_owned_review(ReviewInfo $rrow = null) { 2449 return $rrow 2450 && ($rrow->contactId == $this->contactId 2451 || ($this->_review_tokens && $rrow->reviewToken && in_array($rrow->reviewToken, $this->_review_tokens)) 2452 || ($rrow->requestedBy == $this->contactId 2453 && $rrow->reviewType == REVIEW_EXTERNAL 2454 && $this->conf->setting("pcrev_editdelegate"))); 2455 } 2456 2457 function can_view_review_assignment(PaperInfo $prow, $rrow) { 2458 $rights = $this->rights($prow); 2459 return $rights->allow_administer 2460 || $rights->allow_pc 2461 || $rights->review_status != 0 2462 || $this->can_view_review($prow, $rrow); 2463 } 2464 2465 static function can_some_author_respond(PaperInfo $prow) { 2466 return $prow->conf->any_response_open; 2467 } 2468 2469 static function can_some_author_view_submitted_review(PaperInfo $prow) { 2470 if (self::can_some_author_respond($prow)) 2471 return true; 2472 else if ($prow->conf->au_seerev == Conf::AUSEEREV_TAGS) 2473 return $prow->has_any_tag($prow->conf->tag_au_seerev); 2474 else 2475 return $prow->conf->au_seerev != 0; 2476 } 2477 2478 private function can_view_submitted_review_as_author(PaperInfo $prow) { 2479 return self::can_some_author_respond($prow) 2480 || $this->conf->au_seerev == Conf::AUSEEREV_YES 2481 || ($this->conf->au_seerev == Conf::AUSEEREV_UNLESSINCOMPLETE 2482 && (!$this->has_review() 2483 || !$this->has_outstanding_review())) 2484 || ($this->conf->au_seerev == Conf::AUSEEREV_TAGS 2485 && $prow->has_any_tag($this->conf->tag_au_seerev)); 2486 } 2487 2488 function can_view_some_review() { 2489 return $this->is_reviewer() 2490 || ($this->is_author() 2491 && ($this->conf->au_seerev != 0 2492 || $this->conf->any_response_open)); 2493 } 2494 2495 private function seerev_setting(PaperInfo $prow, $rrow, $rights) { 2496 $round = $rrow ? $rrow->reviewRound : "max"; 2497 if ($rights->allow_pc) { 2498 $rs = $this->conf->round_setting("pc_seeallrev", $round); 2499 if (!$this->conf->has_tracks()) 2500 return $rs; 2501 if ($this->conf->check_required_tracks($prow, $this, Track::VIEWREVOVERRIDE)) 2502 return Conf::PCSEEREV_YES; 2503 if ($this->conf->check_tracks($prow, $this, Track::VIEWREV)) { 2504 if (!$this->conf->check_tracks($prow, $this, Track::VIEWALLREV)) 2505 $rs = 0; 2506 return $rs; 2507 } 2508 } else { 2509 if ($this->conf->round_setting("extrev_view", $round)) 2510 return 0; 2511 } 2512 return -1; 2513 } 2514 2515 private function seerevid_setting(PaperInfo $prow, $rrow, $rights) { 2516 $round = $rrow ? $rrow->reviewRound : "max"; 2517 if ($rights->allow_pc) { 2518 if ($this->conf->check_required_tracks($prow, $this, Track::VIEWREVOVERRIDE)) 2519 return Conf::PCSEEREV_YES; 2520 if ($this->conf->check_tracks($prow, $this, Track::VIEWREVID)) { 2521 $s = $this->conf->round_setting("pc_seeblindrev", $round); 2522 if ($s >= 0) 2523 return $s ? 0 : Conf::PCSEEREV_YES; 2524 } 2525 } else { 2526 if ($this->conf->round_setting("extrev_view", $round) == 2) 2527 return 0; 2528 } 2529 return -1; 2530 } 2531 2532 function can_view_review(PaperInfo $prow, $rrow, $forceShow = null, $viewscore = null) { 2533 if (is_int($rrow)) { 2534 $viewscore = $rrow; 2535 $rrow = null; 2536 } else if ($viewscore === null) 2537 $viewscore = VIEWSCORE_AUTHOR; 2538 if ($rrow && !($rrow instanceof ReviewInfo)) 2539 error_log("not ReviewInfo " . json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS))); 2540 assert(!$rrow || $prow->paperId == $rrow->paperId); 2541 $rights = $this->rights($prow, $forceShow); 2542 if ($this->_can_administer_for_track($prow, $rights, Track::VIEWREV) 2543 || $rights->reviewType == REVIEW_META 2544 || ($rrow 2545 && $this->is_owned_review($rrow) 2546 && $viewscore >= VIEWSCORE_REVIEWERONLY)) 2547 return true; 2548 $rrowSubmitted = !$rrow || $rrow->reviewSubmitted > 0; 2549 $seerev = $this->seerev_setting($prow, $rrow, $rights); 2550 // See also PaperInfo::can_view_review_identity_of. 2551 return ($rights->act_author_view 2552 && $rrowSubmitted 2553 && (!$rrow || $rrow->reviewOrdinal > 0) 2554 && $this->can_view_submitted_review_as_author($prow) 2555 && ($viewscore >= VIEWSCORE_AUTHOR 2556 || ($viewscore >= VIEWSCORE_AUTHORDEC 2557 && $prow->outcome 2558 && $this->can_view_decision($prow, $forceShow)))) 2559 || ($rights->allow_pc 2560 && $rrowSubmitted 2561 && $viewscore >= VIEWSCORE_PC 2562 && $seerev > 0 2563 && ($seerev != Conf::PCSEEREV_UNLESSANYINCOMPLETE 2564 || !$this->has_outstanding_review()) 2565 && ($seerev != Conf::PCSEEREV_UNLESSINCOMPLETE 2566 || !$rights->review_status)) 2567 || ($rights->review_status != 0 2568 && !$rights->view_conflict_type 2569 && $rrowSubmitted 2570 && $viewscore >= VIEWSCORE_PC 2571 && $prow->review_not_incomplete($this) 2572 && $seerev >= 0); 2573 } 2574 2575 function perm_view_review(PaperInfo $prow, $rrow, $forceShow = null, $viewscore = null) { 2576 if ($this->can_view_review($prow, $rrow, $forceShow, $viewscore)) 2577 return null; 2578 $rrowSubmitted = !$rrow || $rrow->reviewSubmitted > 0; 2579 $rights = $this->rights($prow, $forceShow); 2580 $whyNot = $prow->make_whynot(); 2581 if ((!$rights->act_author_view 2582 && !$rights->allow_pc 2583 && !$rights->review_status) 2584 || ($rights->allow_pc 2585 && !$this->conf->check_tracks($prow, $this, Track::VIEWREV))) 2586 $whyNot["permission"] = "view_review"; 2587 else if ($prow->timeWithdrawn > 0) 2588 $whyNot["withdrawn"] = 1; 2589 else if ($prow->timeSubmitted <= 0) 2590 $whyNot["notSubmitted"] = 1; 2591 else if ($rights->act_author_view 2592 && $this->conf->au_seerev == Conf::AUSEEREV_UNLESSINCOMPLETE 2593 && $this->has_outstanding_review() 2594 && $this->has_review()) 2595 $whyNot["reviewsOutstanding"] = 1; 2596 else if ($rights->act_author_view 2597 && !$rrowSubmitted) 2598 $whyNot["permission"] = "view_review"; 2599 else if ($rights->act_author_view) 2600 $whyNot["deadline"] = "au_seerev"; 2601 else if ($rights->view_conflict_type) 2602 $whyNot["conflict"] = 1; 2603 else if (!$rights->allow_pc 2604 && $prow->review_submitted($this)) 2605 $whyNot["externalReviewer"] = 1; 2606 else if (!$rrowSubmitted) 2607 $whyNot["reviewNotSubmitted"] = 1; 2608 else if ($rights->allow_pc 2609 && $this->seerev_setting($prow, $rrow, $rights) == Conf::PCSEEREV_UNLESSANYINCOMPLETE 2610 && $this->has_outstanding_review()) 2611 $whyNot["reviewsOutstanding"] = 1; 2612 else if (!$this->conf->time_review_open()) 2613 $whyNot["deadline"] = "rev_open"; 2614 else 2615 $whyNot["reviewNotComplete"] = 1; 2616 if ($rights->allow_administer) 2617 $whyNot["forceShow"] = 1; 2618 return $whyNot; 2619 } 2620 2621 function can_view_review_identity(PaperInfo $prow, ReviewInfo $rrow = null, $forceShow = null) { 2622 $rights = $this->rights($prow, $forceShow); 2623 // See also PaperInfo::can_view_review_identity_of. 2624 // See also ReviewerFexpr. 2625 if ($this->_can_administer_for_track($prow, $rights, Track::VIEWREVID) 2626 || $rights->reviewType == REVIEW_META 2627 || ($rrow && $rrow->requestedBy == $this->contactId && $rights->allow_pc) 2628 || ($rrow && $this->is_owned_review($rrow))) 2629 return true; 2630 $seerevid_setting = $this->seerevid_setting($prow, $rrow, $rights); 2631 return ($rights->allow_pc 2632 && $seerevid_setting == Conf::PCSEEREV_YES) 2633 || ($rights->allow_review 2634 && $prow->review_not_incomplete($this) 2635 && $seerevid_setting >= 0) 2636 || !$this->conf->is_review_blind($rrow); 2637 } 2638 2639 function can_view_some_review_identity() { 2640 $tags = ""; 2641 if (($t = $this->conf->permissive_track_tag_for($this, Track::VIEWREVOVERRIDE)) 2642 || ($t = $this->conf->permissive_track_tag_for($this, Track::VIEWREVID))) 2643 $tags = " $t#0 "; 2644 if ($this->isPC) 2645 $rtype = $this->is_metareviewer() ? REVIEW_META : REVIEW_PC; 2646 else 2647 $rtype = $this->is_reviewer() ? REVIEW_EXTERNAL : 0; 2648 $prow = new PaperInfo([ 2649 "conflictType" => 0, "managerContactId" => 0, 2650 "myReviewPermissions" => "$rtype 1 0", 2651 "paperId" => 1, "timeSubmitted" => 1, 2652 "blind" => false, "outcome" => 1, 2653 "paperTags" => $tags 2654 ], $this); 2655 $overrides = $this->add_overrides(self::OVERRIDE_CONFLICT); 2656 $answer = $this->can_view_review_identity($prow, null); 2657 $this->set_overrides($overrides); 2658 return $answer; 2659 } 2660 2661 function can_view_review_round(PaperInfo $prow, ReviewInfo $rrow = null) { 2662 $rights = $this->rights($prow); 2663 return $rights->can_administer 2664 || $rights->allow_pc 2665 || $rights->allow_review; 2666 } 2667 2668 function can_view_review_time(PaperInfo $prow, ReviewInfo $rrow = null) { 2669 $rights = $this->rights($prow); 2670 return !$rights->act_author_view 2671 || ($rrow && $rrow->reviewAuthorSeen 2672 && $rrow->reviewAuthorSeen <= $rrow->reviewAuthorModified); 2673 } 2674 2675 function can_view_review_requester(PaperInfo $prow, ReviewInfo $rrow = null) { 2676 $rights = $this->rights($prow); 2677 return $this->_can_administer_for_track($prow, $rights, Track::VIEWREVID) 2678 || ($rrow && $rrow->requestedBy == $this->contactId && $rights->allow_pc) 2679 || ($rrow && $this->is_owned_review($rrow)) 2680 || ($rights->allow_pc && $this->can_view_review_identity($prow, $rrow)); 2681 } 2682 2683 function can_request_review(PaperInfo $prow, $check_time) { 2684 $rights = $this->rights($prow); 2685 return ($rights->reviewType >= REVIEW_PC 2686 || ($this->contactId > 0 2687 && isset($prow->leadContactId) 2688 && $prow->leadContactId == $this->contactId) 2689 || $rights->allow_administer) 2690 && (!$check_time 2691 || $this->conf->time_review(null, false, true) 2692 || $this->override_deadlines($rights)); 2693 } 2694 2695 function perm_request_review(PaperInfo $prow, $check_time) { 2696 if ($this->can_request_review($prow, $check_time)) 2697 return null; 2698 $rights = $this->rights($prow); 2699 $whyNot = $prow->make_whynot(); 2700 if ($rights->reviewType < REVIEW_PC 2701 && ($this->contactId <= 0 2702 || !isset($prow->leadContactId) 2703 || $prow->leadContactId != $this->contactId) 2704 && !$rights->allow_administer) 2705 $whyNot["permission"] = "request_review"; 2706 else { 2707 $whyNot["deadline"] = ($rights->allow_pc ? "pcrev_hard" : "extrev_hard"); 2708 if ($rights->allow_administer) 2709 $whyNot["override"] = 1; 2710 } 2711 return $whyNot; 2712 } 2713 2714 function can_review_any() { 2715 return $this->isPC 2716 && $this->conf->setting("pcrev_any") > 0 2717 && $this->conf->time_review(null, true, true) 2718 && $this->conf->check_any_tracks($this, Track::UNASSREV); 2719 } 2720 2721 function timeReview(PaperInfo $prow, ReviewInfo $rrow = null) { 2722 $rights = $this->rights($prow); 2723 if ($rights->reviewType > 0 2724 || ($rrow 2725 && $this->is_owned_review($rrow)) 2726 || ($rrow 2727 && $rrow->contactId != $this->contactId 2728 && $rights->allow_administer)) 2729 return $this->conf->time_review($rrow, $rights->allow_pc, true); 2730 else if ($rights->allow_review 2731 && $this->conf->setting("pcrev_any") > 0) 2732 return $this->conf->time_review(null, true, true); 2733 else 2734 return false; 2735 } 2736 2737 function can_become_reviewer_ignore_conflict(PaperInfo $prow = null) { 2738 if (!$prow) 2739 return $this->isPC 2740 && ($this->conf->check_all_tracks($this, Track::ASSREV) 2741 || $this->conf->check_all_tracks($this, Track::UNASSREV)); 2742 $rights = $this->rights($prow); 2743 return $rights->allow_pc_broad 2744 && ($rights->reviewType > 0 2745 || $rights->allow_administer 2746 || $this->conf->check_tracks($prow, $this, Track::ASSREV) 2747 || $this->conf->check_tracks($prow, $this, Track::UNASSREV)); 2748 } 2749 2750 function can_accept_review_assignment_ignore_conflict(PaperInfo $prow = null) { 2751 if (!$prow) 2752 return $this->isPC && $this->conf->check_all_tracks($this, Track::ASSREV); 2753 $rights = $this->rights($prow); 2754 return ($rights->allow_administer 2755 || $this->isPC) 2756 && ($rights->reviewType > 0 2757 || $rights->allow_administer 2758 || $this->conf->check_tracks($prow, $this, Track::ASSREV)); 2759 } 2760 2761 function can_accept_review_assignment(PaperInfo $prow) { 2762 $rights = $this->rights($prow); 2763 return ($rights->allow_pc 2764 || ($this->isPC && !$rights->conflictType)) 2765 && ($rights->reviewType > 0 2766 || $rights->allow_administer 2767 || $this->conf->check_tracks($prow, $this, Track::ASSREV)); 2768 } 2769 2770 private function rights_owned_review($rights, $rrow) { 2771 if ($rrow) 2772 return $rights->can_administer || $this->is_owned_review($rrow); 2773 else 2774 return $rights->reviewType > 0; 2775 } 2776 2777 function can_review(PaperInfo $prow, ReviewInfo $rrow = null, $submit = false) { 2778 assert(!$rrow || $rrow->paperId == $prow->paperId); 2779 $rights = $this->rights($prow); 2780 if ($submit && !$this->can_clickthrough("review")) 2781 return false; 2782 return ($this->rights_owned_review($rights, $rrow) 2783 && $this->conf->time_review($rrow, $rights->allow_pc, true)) 2784 || (!$rrow 2785 && $prow->timeSubmitted > 0 2786 && $rights->allow_review 2787 && $this->conf->setting("pcrev_any") > 0 2788 && $this->conf->time_review(null, true, true)) 2789 || ($rights->can_administer 2790 && (($prow->timeSubmitted > 0 && !$submit) 2791 || $this->override_deadlines($rights))); 2792 } 2793 2794 function perm_review(PaperInfo $prow, $rrow, $submit = false) { 2795 if ($this->can_review($prow, $rrow, $submit)) 2796 return null; 2797 $rights = $this->rights($prow); 2798 $rrow_cid = $rrow ? $rrow->contactId : 0; 2799 // The "reviewNotAssigned" and "deadline" failure reasons are special. 2800 // If either is set, the system will still allow review form download. 2801 $whyNot = $prow->make_whynot(); 2802 if ($rrow && $rrow_cid != $this->contactId 2803 && !$rights->allow_administer) 2804 $whyNot["differentReviewer"] = 1; 2805 else if (!$rights->allow_pc && !$this->rights_owned_review($rights, $rrow)) 2806 $whyNot["permission"] = "review"; 2807 else if ($prow->timeWithdrawn > 0) 2808 $whyNot["withdrawn"] = 1; 2809 else if ($prow->timeSubmitted <= 0) 2810 $whyNot["notSubmitted"] = 1; 2811 else { 2812 if ($rights->conflictType && !$rights->can_administer) 2813 $whyNot["conflict"] = 1; 2814 else if ($rights->allow_review 2815 && !$this->rights_owned_review($rights, $rrow) 2816 && (!$rrow || $rrow_cid == $this->contactId)) 2817 $whyNot["reviewNotAssigned"] = 1; 2818 else if ($this->can_review($prow, $rrow, false) 2819 && !$this->can_clickthrough("review")) 2820 $whyNot["clickthrough"] = 1; 2821 else 2822 $whyNot["deadline"] = ($rights->allow_pc ? "pcrev_hard" : "extrev_hard"); 2823 if ($rights->allow_administer 2824 && ($rights->conflictType || $prow->timeSubmitted <= 0)) 2825 $whyNot["forceShow"] = 1; 2826 if ($rights->allow_administer && isset($whyNot["deadline"])) 2827 $whyNot["override"] = 1; 2828 } 2829 return $whyNot; 2830 } 2831 2832 function perm_submit_review(PaperInfo $prow, $rrow) { 2833 return $this->perm_review($prow, $rrow, true); 2834 } 2835 2836 function can_create_review_from(PaperInfo $prow, Contact $user) { 2837 $rights = $this->rights($prow); 2838 return $rights->can_administer 2839 && ($prow->timeSubmitted > 0 || $this->override_deadlines($rights)) 2840 && (!$user->isPC || $user->can_accept_review_assignment($prow)) 2841 && ($this->conf->time_review(null, true, true) || $this->override_deadlines($rights)); 2842 } 2843 2844 function perm_create_review_from(PaperInfo $prow, Contact $user) { 2845 if ($this->can_create_review_from($prow, $user)) 2846 return null; 2847 $rights = $this->rights($prow); 2848 $whyNot = $prow->make_whynot(); 2849 if (!$rights->allow_administer) 2850 $whyNot["administer"] = 1; 2851 else if ($prow->timeWithdrawn > 0) 2852 $whyNot["withdrawn"] = 1; 2853 else if ($prow->timeSubmitted <= 0) 2854 $whyNot["notSubmitted"] = 1; 2855 else { 2856 if ($user->isPC && !$user->can_accept_review_assignment($prow)) 2857 $whyNot["unacceptableReviewer"] = 1; 2858 if (!$this->conf->time_review(null, true, true)) 2859 $whyNot["deadline"] = ($user->isPC ? "pcrev_hard" : "extrev_hard"); 2860 if ($rights->allow_administer 2861 && ($rights->conflictType || $prow->timeSubmitted <= 0)) 2862 $whyNot["forceShow"] = 1; 2863 if ($rights->allow_administer && isset($whyNot["deadline"])) 2864 $whyNot["override"] = 1; 2865 } 2866 return $whyNot; 2867 } 2868 2869 function can_clickthrough($ctype) { 2870 if (!$this->privChair && $this->conf->opt("clickthrough_$ctype")) { 2871 $csha1 = sha1($this->conf->message_html("clickthrough_$ctype")); 2872 $data = $this->data("clickthrough"); 2873 return $data && get($data, $csha1); 2874 } else 2875 return true; 2876 } 2877 2878 function can_view_review_ratings(PaperInfo $prow, ReviewInfo $rrow = null, $override_self = false) { 2879 $rs = $this->conf->setting("rev_ratings"); 2880 $rights = $this->rights($prow); 2881 if (!$this->can_view_review($prow, $rrow) 2882 || (!$rights->allow_pc && !$rights->allow_review) 2883 || ($rs != REV_RATINGS_PC && $rs != REV_RATINGS_PC_EXTERNAL)) 2884 return false; 2885 if (!$rrow 2886 || $override_self 2887 || $rrow->contactId != $this->contactId 2888 || $this->can_administer($prow) 2889 || $this->conf->setting("pc_seeallrev") 2890 || (isset($rrow->allRatings) && strpos($rrow->allRatings, ",") !== false)) 2891 return true; 2892 // Do not show rating counts if rater identity is unambiguous. 2893 // See also PaperSearch::_clauseTermSetRating. 2894 $nsubraters = 0; 2895 foreach ($prow->reviews_by_id() as $rrow) 2896 if ($rrow->reviewNeedsSubmit == 0 2897 && $rrow->contactId != $this->contactId 2898 && ($rs == REV_RATINGS_PC_EXTERNAL 2899 || ($rs == REV_RATINGS_PC && $rrow->reviewType > REVIEW_EXTERNAL))) 2900 ++$nsubraters; 2901 return $nsubraters >= 2; 2902 } 2903 2904 function can_view_some_review_ratings() { 2905 $rs = $this->conf->setting("rev_ratings"); 2906 return $this->is_reviewer() && ($rs == REV_RATINGS_PC || $rs == REV_RATINGS_PC_EXTERNAL); 2907 } 2908 2909 function can_rate_review(PaperInfo $prow, $rrow) { 2910 return $this->can_view_review_ratings($prow, $rrow, true) 2911 && !$this->is_my_review($rrow); 2912 } 2913 2914 2915 function is_my_comment(PaperInfo $prow, $crow) { 2916 if ($crow->contactId == $this->contactId) 2917 return true; 2918 if ($this->_review_tokens) { 2919 foreach ($prow->reviews_of_user($crow->contactId) as $rrow) 2920 if ($rrow->reviewToken && in_array($rrow->reviewToken, $this->_review_tokens)) 2921 return true; 2922 } 2923 return false; 2924 } 2925 2926 function can_comment(PaperInfo $prow, $crow, $submit = false) { 2927 if ($crow && ($crow->commentType & COMMENTTYPE_RESPONSE)) 2928 return $this->can_respond($prow, $crow, $submit); 2929 $rights = $this->rights($prow); 2930 $author = $rights->act_author 2931 && $this->conf->setting("cmt_author") > 0 2932 && $this->can_view_submitted_review_as_author($prow); 2933 return ($author 2934 || ($rights->allow_review 2935 && ($prow->timeSubmitted > 0 2936 || $rights->review_status != 0 2937 || ($rights->allow_administer && $rights->rights_forced)) 2938 && ($this->conf->setting("cmt_always") > 0 2939 || $this->conf->time_review(null, $rights->allow_pc, true) 2940 || ($rights->allow_administer 2941 && (!$submit || $this->override_deadlines($rights)))))) 2942 && (!$crow 2943 || !$crow->contactId 2944 || $rights->allow_administer 2945 || $this->is_my_comment($prow, $crow) 2946 || ($author 2947 && ($crow->commentType & COMMENTTYPE_BYAUTHOR))); 2948 } 2949 2950 function can_submit_comment(PaperInfo $prow, $crow) { 2951 return $this->can_comment($prow, $crow, true); 2952 } 2953 2954 function perm_comment(PaperInfo $prow, $crow, $submit = false) { 2955 if ($crow && ($crow->commentType & COMMENTTYPE_RESPONSE)) 2956 return $this->perm_respond($prow, $crow, $submit); 2957 if ($this->can_comment($prow, $crow, $submit)) 2958 return null; 2959 $rights = $this->rights($prow); 2960 $whyNot = $prow->make_whynot(); 2961 if ($crow && $crow->contactId != $this->contactId 2962 && !$rights->allow_administer) 2963 $whyNot["differentReviewer"] = 1; 2964 else if (!$rights->allow_pc 2965 && !$rights->allow_review 2966 && (!$rights->act_author 2967 || $this->conf->setting("cmt_author", 0) <= 0)) 2968 $whyNot["permission"] = "comment"; 2969 else if ($prow->timeWithdrawn > 0) 2970 $whyNot["withdrawn"] = 1; 2971 else if ($prow->timeSubmitted <= 0) 2972 $whyNot["notSubmitted"] = 1; 2973 else { 2974 if ($rights->conflictType > 0) 2975 $whyNot["conflict"] = 1; 2976 else 2977 $whyNot["deadline"] = ($rights->allow_pc ? "pcrev_hard" : "extrev_hard"); 2978 if ($rights->allow_administer && $rights->conflictType) 2979 $whyNot["forceShow"] = 1; 2980 if ($rights->allow_administer && isset($whyNot['deadline'])) 2981 $whyNot["override"] = 1; 2982 } 2983 return $whyNot; 2984 } 2985 2986 function perm_submit_comment(PaperInfo $prow, $crow) { 2987 return $this->perm_comment($prow, $crow, true); 2988 } 2989 2990 function can_respond(PaperInfo $prow, CommentInfo $crow, $submit = false) { 2991 if ($prow->timeSubmitted <= 0 2992 || !($crow->commentType & COMMENTTYPE_RESPONSE) 2993 || !($rrd = get($prow->conf->resp_rounds(), $crow->commentRound))) 2994 return false; 2995 $rights = $this->rights($prow); 2996 return ($rights->can_administer 2997 || $rights->act_author) 2998 && (($rights->allow_administer 2999 && (!$submit || $this->override_deadlines($rights))) 3000 || $rrd->time_allowed(true)) 3001 && (!$rrd->search 3002 || $rrd->search->test($prow)); 3003 } 3004 3005 function perm_respond(PaperInfo $prow, CommentInfo $crow, $submit = false) { 3006 if ($this->can_respond($prow, $crow, $submit)) 3007 return null; 3008 $rights = $this->rights($prow); 3009 $whyNot = $prow->make_whynot(); 3010 if (!$rights->allow_administer 3011 && !$rights->act_author) 3012 $whyNot["permission"] = "respond"; 3013 else if ($prow->timeWithdrawn > 0) 3014 $whyNot["withdrawn"] = 1; 3015 else if ($prow->timeSubmitted <= 0) 3016 $whyNot["notSubmitted"] = 1; 3017 else { 3018 $whyNot["deadline"] = "resp_done"; 3019 if ($crow->commentRound) 3020 $whyNot["deadline"] .= "_" . $crow->commentRound; 3021 if ($rights->allow_administer && $rights->conflictType) 3022 $whyNot["forceShow"] = 1; 3023 if ($rights->allow_administer) 3024 $whyNot["override"] = 1; 3025 } 3026 return $whyNot; 3027 } 3028 3029 function preferred_resp_round_number(PaperInfo $prow) { 3030 $rights = $this->rights($prow); 3031 if ($rights->act_author) 3032 foreach ($prow->conf->resp_rounds() as $rrd) 3033 if ($rrd->time_allowed()) 3034 return $rrd->number; 3035 return false; 3036 } 3037 3038 function can_view_comment(PaperInfo $prow, $crow, $forceShow = null) { 3039 $ctype = $crow ? $crow->commentType : COMMENTTYPE_AUTHOR; 3040 $rights = $this->rights($prow, $forceShow); 3041 return ($crow && $this->is_my_comment($prow, $crow)) 3042 || $rights->can_administer 3043 || ($rights->act_author_view 3044 && ($ctype & (COMMENTTYPE_BYAUTHOR | COMMENTTYPE_RESPONSE))) 3045 || ($rights->act_author_view 3046 && $ctype >= COMMENTTYPE_AUTHOR 3047 && !($ctype & COMMENTTYPE_DRAFT) 3048 && $this->can_view_submitted_review_as_author($prow)) 3049 || (!$rights->view_conflict_type 3050 && !($ctype & COMMENTTYPE_DRAFT) 3051 && ($rights->allow_pc 3052 ? $ctype >= COMMENTTYPE_PCONLY 3053 : $ctype >= COMMENTTYPE_REVIEWER) 3054 && $this->can_view_review($prow, null, $forceShow) 3055 && ($this->conf->setting("cmt_revid") 3056 || $ctype >= COMMENTTYPE_AUTHOR 3057 || $this->can_view_review_identity($prow, null, $forceShow))); 3058 } 3059 3060 function can_view_new_comment_ignore_conflict(PaperInfo $prow) { 3061 // Goal: Return true if this user is part of the comment mention 3062 // completion for a new comment on $prow. 3063 // Problem: If authors are hidden, should we mention this user or not? 3064 $rights = $this->rights($prow, null); 3065 return $rights->can_administer 3066 || $rights->allow_pc; 3067 } 3068 3069 function canViewCommentReviewWheres() { 3070 if ($this->privChair 3071 || ($this->isPC && $this->conf->setting("pc_seeallrev") > 0)) 3072 return array(); 3073 else 3074 return array("(" . $this->act_author_view_sql("PaperConflict") 3075 . " or MyPaperReview.reviewId is not null)"); 3076 } 3077 3078 function can_view_comment_identity(PaperInfo $prow, $crow, $forceShow = null) { 3079 if ($crow && ($crow->commentType & (COMMENTTYPE_RESPONSE | COMMENTTYPE_BYAUTHOR))) 3080 return $this->can_view_authors($prow, $forceShow); 3081 $rights = $this->rights($prow, $forceShow); 3082 return $this->_can_administer_for_track($prow, $rights, Track::VIEWREVID) 3083 || ($crow && $crow->contactId == $this->contactId) 3084 || (($rights->allow_pc 3085 || ($rights->allow_review 3086 && $this->conf->setting("extrev_view") >= 2)) 3087 && ($this->can_view_review_identity($prow, null) 3088 || ($crow && $prow->can_view_review_identity_of($crow->commentId, $this)))) 3089 || !$this->conf->is_review_blind(!$crow || ($crow->commentType & COMMENTTYPE_BLIND) != 0); 3090 } 3091 3092 function can_view_comment_time(PaperInfo $prow, $crow) { 3093 return $this->can_view_comment_identity($prow, $crow, true); 3094 } 3095 3096 function can_view_comment_tags(PaperInfo $prow, $crow) { 3097 $rights = $this->rights($prow); 3098 return $rights->allow_pc || $rights->review_status != 0; 3099 } 3100 3101 function can_view_some_draft_response() { 3102 return $this->is_manager() || $this->is_author(); 3103 } 3104 3105 3106 function can_view_decision(PaperInfo $prow, $forceShow = null) { 3107 $rights = $this->rights($prow, $forceShow); 3108 return $rights->can_administer 3109 || ($rights->act_author_view 3110 && $prow->can_author_view_decision()) 3111 || ($rights->allow_pc_broad 3112 && $this->conf->timePCViewDecision($rights->view_conflict_type > 0)) 3113 || ($rights->review_status > 0 3114 && $this->conf->time_reviewer_view_decision()); 3115 } 3116 3117 function can_view_some_decision() { 3118 return $this->is_manager() 3119 || ($this->is_author() && $this->can_view_some_decision_as_author()) 3120 || ($this->isPC && $this->conf->timePCViewDecision(false)) 3121 || ($this->is_reviewer() && $this->conf->time_reviewer_view_decision()); 3122 } 3123 3124 function can_view_some_decision_as_author() { 3125 return $this->conf->can_some_author_view_decision(); 3126 } 3127 3128 static function can_some_author_view_decision(PaperInfo $prow) { 3129 return $prow->outcome 3130 && $prow->conf->can_some_author_view_decision(); 3131 } 3132 3133 function can_set_decision(PaperInfo $prow) { 3134 return $this->can_administer($prow); 3135 } 3136 3137 function can_set_some_decision() { 3138 return $this->can_administer(null); 3139 } 3140 3141 function can_view_formula(Formula $formula, $as_author = false) { 3142 $bound = $this->permissive_view_score_bound($as_author); 3143 return $formula->view_score($this) > $bound; 3144 } 3145 3146 function can_edit_formula(Formula $formula) { 3147 return $this->privChair || ($this->isPC && $formula->createdBy > 0); 3148 } 3149 3150 // A review field is visible only if its view_score > view_score_bound. 3151 function view_score_bound(PaperInfo $prow, ReviewInfo $rrow = null) { 3152 // Returns the maximum view_score for an invisible review 3153 // field. Values are: 3154 // VIEWSCORE_ADMINONLY admin can view 3155 // VIEWSCORE_REVIEWERONLY ... and review author can view 3156 // VIEWSCORE_PC ... and any PC/reviewer can view 3157 // VIEWSCORE_AUTHORDEC ... and authors can view when decisions visible 3158 // VIEWSCORE_AUTHOR ... and authors can view 3159 // So returning -3 means all scores are visible. 3160 // Deadlines are not considered. 3161 $rights = $this->rights($prow); 3162 if ($rights->can_administer) 3163 return VIEWSCORE_ADMINONLY - 1; 3164 else if ($rrow ? $this->is_owned_review($rrow) : $rights->allow_review) 3165 return VIEWSCORE_REVIEWERONLY - 1; 3166 else if (!$this->can_view_review($prow, $rrow)) 3167 return VIEWSCORE_MAX + 1; 3168 else if ($rights->act_author_view 3169 && $prow->outcome 3170 && $this->can_view_decision($prow)) 3171 return VIEWSCORE_AUTHORDEC - 1; 3172 else if ($rights->act_author_view) 3173 return VIEWSCORE_AUTHOR - 1; 3174 else 3175 return VIEWSCORE_PC - 1; 3176 } 3177 3178 function permissive_view_score_bound($as_author = false) { 3179 if (!$as_author && $this->is_manager()) { 3180 return VIEWSCORE_ADMINONLY - 1; 3181 } else if (!$as_author && $this->is_reviewer()) { 3182 return VIEWSCORE_REVIEWERONLY - 1; 3183 } else if (($as_author || $this->is_author()) 3184 && ($this->conf->any_response_open 3185 || $this->conf->au_seerev != 0)) { 3186 if ($this->can_view_some_decision_as_author()) { 3187 return VIEWSCORE_AUTHORDEC - 1; 3188 } else { 3189 return VIEWSCORE_AUTHOR - 1; 3190 } 3191 } else { 3192 return VIEWSCORE_MAX + 1; 3193 } 3194 } 3195 3196 function can_view_tags(PaperInfo $prow = null) { 3197 // see also AllTags_API::alltags 3198 if (!$prow) 3199 return $this->isPC; 3200 $rights = $this->rights($prow); 3201 return $rights->allow_pc 3202 || ($rights->allow_pc_broad && $this->conf->tag_seeall) 3203 || (($this->privChair || $rights->allow_administer) 3204 && $this->conf->tags()->has_sitewide); 3205 } 3206 3207 function can_view_most_tags(PaperInfo $prow = null) { 3208 if (!$prow) 3209 return $this->isPC; 3210 $rights = $this->rights($prow); 3211 return $rights->allow_pc 3212 || ($rights->allow_pc_broad && $this->conf->tag_seeall); 3213 } 3214 3215 function can_view_hidden_tags(PaperInfo $prow = null) { 3216 if (!$prow) 3217 return $this->privChair; 3218 $rights = $this->rights($prow); 3219 return $rights->can_administer 3220 || $this->conf->check_required_tracks($prow, $this, Track::HIDDENTAG); 3221 } 3222 3223 function can_view_tag(PaperInfo $prow, $tag) { 3224 if ($this->_overrides & self::OVERRIDE_TAG_CHECKS) 3225 return true; 3226 $rights = $this->rights($prow); 3227 $tag = TagInfo::base($tag); 3228 $twiddle = strpos($tag, "~"); 3229 $dt = $this->conf->tags(); 3230 return ($rights->allow_pc 3231 || ($rights->allow_pc_broad && $this->conf->tag_seeall) 3232 || ($this->privChair && $dt->is_sitewide($tag))) 3233 && ($rights->allow_administer 3234 || $twiddle === false 3235 || ($twiddle === 0 && $tag[1] !== "~") 3236 || ($twiddle > 0 3237 && (substr($tag, 0, $twiddle) == $this->contactId 3238 || $dt->is_votish(substr($tag, $twiddle + 1))))) 3239 && ($twiddle !== false 3240 || !$dt->has_hidden 3241 || !$dt->is_hidden($tag) 3242 || $this->can_view_hidden_tags($prow)); 3243 } 3244 3245 function can_view_peruser_tags(PaperInfo $prow, $tag) { 3246 return $this->can_view_tag($prow, ($this->contactId + 1) . "~$tag"); 3247 } 3248 3249 function can_view_any_peruser_tags($tag) { 3250 return $this->is_manager() 3251 || ($this->isPC && $this->conf->tags()->is_votish($tag)); 3252 } 3253 3254 function can_change_tag(PaperInfo $prow, $tag, $previndex, $index) { 3255 if (($this->_overrides & self::OVERRIDE_TAG_CHECKS) 3256 || $this->is_site_contact) 3257 return true; 3258 $rights = $this->rights($prow); 3259 $tagmap = $this->conf->tags(); 3260 if (!($rights->allow_pc 3261 && ($rights->can_administer || $this->conf->timePCViewPaper($prow, false)))) { 3262 if ($this->privChair && $tagmap->has_sitewide) { 3263 if (!$tag) 3264 return true; 3265 else { 3266 $dt = $tagmap->check($tag); 3267 return $dt && $dt->sitewide && !$dt->autosearch; 3268 } 3269 } else 3270 return false; 3271 } 3272 if (!$tag) 3273 return true; 3274 $tag = TagInfo::base($tag); 3275 $twiddle = strpos($tag, "~"); 3276 if ($twiddle === 0 && $tag[1] === "~") { 3277 if (!$rights->can_administer) 3278 return false; 3279 else if (!$tagmap->has_autosearch) 3280 return true; 3281 else { 3282 $dt = $tagmap->check($tag); 3283 return !$dt || !$dt->autosearch; 3284 } 3285 } 3286 if ($twiddle > 0 3287 && substr($tag, 0, $twiddle) != $this->contactId 3288 && !$rights->can_administer) 3289 return false; 3290 if ($twiddle !== false) { 3291 $t = $this->conf->tags()->check(substr($tag, $twiddle + 1)); 3292 return !($t && $t->vote && $index < 0); 3293 } else { 3294 $t = $this->conf->tags()->check($tag); 3295 if (!$t) 3296 return true; 3297 else if ($t->vote 3298 || $t->approval 3299 || ($t->track && !$this->privChair) 3300 || ($t->hidden && !$this->can_view_hidden_tags($prow)) 3301 || $t->autosearch) 3302 return false; 3303 else 3304 return $rights->can_administer 3305 || ($this->privChair && $t->sitewide) 3306 || (!$t->readonly && !$t->rank); 3307 } 3308 } 3309 3310 function perm_change_tag(PaperInfo $prow, $tag, $previndex, $index) { 3311 if ($this->can_change_tag($prow, $tag, $previndex, $index)) 3312 return null; 3313 $rights = $this->rights($prow); 3314 $whyNot = $prow->make_whynot(); 3315 $whyNot["tag"] = $tag; 3316 if (!$this->isPC) 3317 $whyNot["permission"] = "change_tag"; 3318 else if ($rights->conflictType > 0) { 3319 $whyNot["conflict"] = true; 3320 if ($rights->allow_administer) 3321 $whyNot["forceShow"] = true; 3322 } else if (!$this->conf->timePCViewPaper($prow, false)) { 3323 if ($prow->timeWithdrawn > 0) 3324 $whyNot["withdrawn"] = true; 3325 else 3326 $whyNot["notSubmitted"] = true; 3327 } else { 3328 $tag = TagInfo::base($tag); 3329 $twiddle = strpos($tag, "~"); 3330 if ($twiddle === 0 && $tag[1] === "~") 3331 $whyNot["chairTag"] = true; 3332 else if ($twiddle > 0 && substr($tag, 0, $twiddle) != $this->contactId) 3333 $whyNot["otherTwiddleTag"] = true; 3334 else if ($twiddle !== false) 3335 $whyNot["voteTagNegative"] = true; 3336 else { 3337 $t = $this->conf->tags()->check($tag); 3338 if ($t && $t->vote) 3339 $whyNot["voteTag"] = true; 3340 else if ($t && $t->autosearch) 3341 $whyNot["autosearchTag"] = true; 3342 else 3343 $whyNot["chairTag"] = true; 3344 } 3345 } 3346 return $whyNot; 3347 } 3348 3349 function can_change_some_tag(PaperInfo $prow = null) { 3350 if (!$prow) 3351 return $this->isPC; 3352 else 3353 return $this->can_change_tag($prow, null, null, null); 3354 } 3355 3356 function perm_change_some_tag(PaperInfo $prow) { 3357 return $this->perm_change_tag($prow, null, null, null); 3358 } 3359 3360 function can_change_tag_anno($tag) { 3361 if ($this->privChair) 3362 return true; 3363 $twiddle = strpos($tag, "~"); 3364 $t = $this->conf->tags()->check($tag); 3365 return $this->isPC 3366 && (!$t || (!$t->readonly && !$t->hidden)) 3367 && ($twiddle === false 3368 || ($twiddle === 0 && $tag[1] !== "~") 3369 || ($twiddle > 0 && substr($tag, 0, $twiddle) == $this->contactId)); 3370 } 3371 3372 function can_view_reviewer_tags(PaperInfo $prow = null) { 3373 return $this->act_pc($prow); 3374 } 3375 3376 3377 function aucollab_matchers() { 3378 if ($this->_aucollab_matchers === null) { 3379 $this->_aucollab_matchers = [new AuthorMatcher($this)]; 3380 if ((string) $this->collaborators !== "") 3381 foreach (explode("\n", $this->collaborators) as $co) { 3382 if (($m = AuthorMatcher::make_collaborator_line($co))) 3383 $this->_aucollab_matchers[] = $m; 3384 } 3385 } 3386 return $this->_aucollab_matchers; 3387 } 3388 3389 function aucollab_general_pregexes() { 3390 if ($this->_aucollab_general_pregexes === null) { 3391 $l = []; 3392 foreach ($this->aucollab_matchers() as $matcher) 3393 if (($r = $matcher->general_pregexes())) 3394 $l[] = $r; 3395 $this->_aucollab_general_pregexes = Text::merge_pregexes($l); 3396 } 3397 return $this->_aucollab_general_pregexes; 3398 } 3399 3400 function full_matcher() { 3401 $this->aucollab_matchers(); 3402 return $this->_aucollab_matchers[0]; 3403 } 3404 3405 function au_general_pregexes() { 3406 return $this->full_matcher()->general_pregexes(); 3407 } 3408 3409 3410 // following / email notifications 3411 3412 function following_reviews(PaperInfo $prow, $watch) { 3413 if ($watch & self::WATCH_REVIEW_EXPLICIT) 3414 return ($watch & self::WATCH_REVIEW) != 0; 3415 else 3416 return ($this->defaultWatch & self::WATCH_REVIEW_ALL) 3417 || (($this->defaultWatch & self::WATCH_REVIEW_MANAGED) 3418 && $this->allow_administer($prow)) 3419 || (($this->defaultWatch & self::WATCH_REVIEW) 3420 && ($prow->has_author($this) 3421 || $prow->has_reviewer($this) 3422 || $prow->has_commenter($this))); 3423 } 3424 3425 3426 // deadlines 3427 3428 function my_deadlines($prows = null) { 3429 // Return cleaned deadline-relevant settings that this user can see. 3430 global $Now; 3431 $dl = (object) ["now" => $Now, "email" => $this->email ? : null]; 3432 if ($this->privChair) 3433 $dl->is_admin = true; 3434 if ($this->is_author()) 3435 $dl->is_author = true; 3436 $dl->sub = (object) []; 3437 $graces = []; 3438 3439 // submissions 3440 $sub_reg = $this->conf->setting("sub_reg"); 3441 $sub_update = $this->conf->setting("sub_update"); 3442 $sub_sub = $this->conf->setting("sub_sub"); 3443 $dl->sub->open = +$this->conf->setting("sub_open") > 0; 3444 $dl->sub->sub = +$sub_sub; 3445 if ($dl->sub->open) 3446 $graces[] = [$dl->sub, "sub_grace"]; 3447 if ($sub_reg && (!$sub_update || $sub_reg < $sub_update)) 3448 $dl->sub->reg = $sub_reg; 3449 if ($sub_update && $sub_update != $sub_sub) 3450 $dl->sub->update = $sub_update; 3451 3452 $sb = $this->conf->submission_blindness(); 3453 if ($sb === Conf::BLIND_ALWAYS) 3454 $dl->sub->blind = true; 3455 else if ($sb === Conf::BLIND_OPTIONAL) 3456 $dl->sub->blind = "optional"; 3457 else if ($sb === Conf::BLIND_UNTILREVIEW) 3458 $dl->sub->blind = "until-review"; 3459 3460 // responses 3461 if ($this->conf->setting("resp_active") > 0 3462 && ($this->isPC || $this->is_author())) { 3463 $dlresps = []; 3464 foreach ($this->conf->resp_rounds() as $rrd) 3465 if ($rrd->open 3466 && ($this->isPC || $rrd->open < $Now) 3467 && ($this->isPC || !$rrd->search || $rrd->search->filter($this->authored_papers()))) { 3468 $dlresp = (object) ["open" => $rrd->open, "done" => +$rrd->done]; 3469 $dlresps[$rrd->name] = $dlresp; 3470 $graces[] = [$dlresp, $rrd->grace]; 3471 } 3472 if (!empty($dlresps)) 3473 $dl->resps = $dlresps; 3474 } 3475 3476 // final copy deadlines 3477 if ($this->conf->setting("final_open") > 0) { 3478 $dl->final = (object) array("open" => true); 3479 $final_soft = +$this->conf->setting("final_soft"); 3480 if ($final_soft > $Now) 3481 $dl->final->done = $final_soft; 3482 else { 3483 $dl->final->done = +$this->conf->setting("final_done"); 3484 $dl->final->ishard = true; 3485 } 3486 $graces[] = [$dl->final, "final_grace"]; 3487 } 3488 3489 // reviewer deadlines 3490 $revtypes = array(); 3491 $rev_open = +$this->conf->setting("rev_open"); 3492 $rev_open = $rev_open > 0 && $rev_open <= $Now; 3493 if ($this->is_reviewer() && $rev_open) 3494 $dl->rev = (object) ["open" => true]; 3495 else if ($this->privChair) 3496 $dl->rev = (object) []; 3497 if (get($dl, "rev")) { 3498 $dl->revs = []; 3499 $k = $this->isPC ? "pcrev" : "extrev"; 3500 foreach ($this->conf->defined_round_list() as $i => $round_name) { 3501 $isuf = $i ? "_$i" : ""; 3502 $s = +$this->conf->setting("{$k}_soft$isuf"); 3503 $h = +$this->conf->setting("{$k}_hard$isuf"); 3504 $dl->revs[$round_name] = $dlround = (object) []; 3505 if ($rev_open) 3506 $dlround->open = true; 3507 if ($h && ($h < $Now || $s < $Now)) { 3508 $dlround->done = $h; 3509 $dlround->ishard = true; 3510 } else if ($s) 3511 $dlround->done = $s; 3512 } 3513 // blindness 3514 $rb = $this->conf->review_blindness(); 3515 if ($rb === Conf::BLIND_ALWAYS) 3516 $dl->rev->blind = true; 3517 else if ($rb === Conf::BLIND_OPTIONAL) 3518 $dl->rev->blind = "optional"; 3519 } 3520 3521 // grace periods: give a minute's notice of an impending grace 3522 // period 3523 foreach ($graces as $g) { 3524 if (($grace = $this->conf->setting($g[1]))) 3525 foreach (array("reg", "update", "sub", "done") as $k) 3526 if (get($g[0], $k) && $g[0]->$k + 60 < $Now 3527 && $g[0]->$k + $grace >= $Now) { 3528 $kgrace = "{$k}_ingrace"; 3529 $g[0]->$kgrace = true; 3530 } 3531 } 3532 3533 // add meeting tracker 3534 if (($this->isPC || $this->tracker_kiosk_state) 3535 && $this->can_view_tracker()) { 3536 $tracker = MeetingTracker::lookup($this->conf); 3537 if ($tracker->trackerid 3538 && ($tinfo = MeetingTracker::info_for($this))) { 3539 $dl->tracker = $tinfo; 3540 $dl->tracker_status = MeetingTracker::tracker_status($tracker); 3541 $dl->now = microtime(true); 3542 } 3543 if ($tracker->position_at) 3544 $dl->tracker_status_at = $tracker->position_at; 3545 if (($tcs = $this->conf->opt("trackerCometSite"))) 3546 $dl->tracker_site = $tcs; 3547 } 3548 3549 // permissions 3550 if ($prows) { 3551 if (is_object($prows)) 3552 $prows = array($prows); 3553 $dl->perm = array(); 3554 foreach ($prows as $prow) { 3555 if (!$this->can_view_paper($prow)) 3556 continue; 3557 $perm = $dl->perm[$prow->paperId] = (object) array(); 3558 $rights = $this->rights($prow); 3559 $admin = $rights->allow_administer; 3560 if ($admin) 3561 $perm->allow_administer = true; 3562 if ($rights->act_author) 3563 $perm->act_author = true; 3564 if ($rights->act_author_view) 3565 $perm->act_author_view = true; 3566 if ($this->can_review($prow, null, false)) 3567 $perm->can_review = true; 3568 if ($this->can_comment($prow, null, true)) 3569 $perm->can_comment = true; 3570 else if ($admin && $this->can_comment($prow, null, false)) 3571 $perm->can_comment = "override"; 3572 if (get($dl, "resps")) { 3573 foreach ($this->conf->resp_rounds() as $rrd) { 3574 $crow = CommentInfo::make_response_template($rrd->number, $prow); 3575 $v = false; 3576 if ($this->can_respond($prow, $crow, true)) 3577 $v = true; 3578 else if ($admin && $this->can_respond($prow, $crow, false)) 3579 $v = "override"; 3580 if ($v && !isset($perm->can_respond)) 3581 $perm->can_responds = []; 3582 if ($v) 3583 $perm->can_responds[$rrd->name] = $v; 3584 } 3585 } 3586 if (self::can_some_author_view_submitted_review($prow)) 3587 $perm->some_author_can_view_review = true; 3588 if (self::can_some_author_view_decision($prow)) 3589 $perm->some_author_can_view_decision = true; 3590 if ($this->isPC 3591 && !$this->conf->can_some_external_reviewer_view_comment()) 3592 $perm->default_comment_visibility = "pc"; 3593 if ($this->_review_tokens) { 3594 $tokens = []; 3595 foreach ($prow->reviews_by_id() as $rrow) { 3596 if ($rrow->reviewToken && in_array($rrow->reviewToken, $this->_review_tokens)) 3597 $tokens[$rrow->reviewToken] = true; 3598 } 3599 if (!empty($tokens)) 3600 $perm->review_tokens = array_map("encode_token", array_keys($tokens)); 3601 } 3602 } 3603 } 3604 3605 return $dl; 3606 } 3607 3608 function has_reportable_deadline() { 3609 global $Now; 3610 $dl = $this->my_deadlines(); 3611 if (get($dl->sub, "reg") || get($dl->sub, "update") || get($dl->sub, "sub")) 3612 return true; 3613 if (get($dl, "resps")) 3614 foreach ($dl->resps as $dlr) { 3615 if (get($dlr, "open") && $dlr->open < $Now && get($dlr, "done")) 3616 return true; 3617 } 3618 if (get($dl, "rev") && get($dl->rev, "open") && $dl->rev->open < $Now) 3619 foreach ($dl->revs as $dlr) { 3620 if (get($dlr, "done")) 3621 return true; 3622 } 3623 return false; 3624 } 3625 3626 3627 function setsession_api($v) { 3628 $ok = true; 3629 preg_match_all('/(?:\A|\s)(foldpaper[abpt]|foldpscollab|foldhomeactivity|(?:pl|pf|ul)display|scoresort)(|\.[^=]*)(=\S*|)(?=\s|\z)/', $v, $ms, PREG_SET_ORDER); 3630 foreach ($ms as $m) { 3631 if ($m[2]) { 3632 $on = intval(substr($m[3], 1) ? : "0") == 0; 3633 if ($m[1] === "pldisplay" || $m[1] === "pfdisplay") 3634 PaperList::change_display($this, substr($m[1], 0, 2), substr($m[2], 1), $on); 3635 else if (preg_match('/\A\.[-a-zA-Z0-9_:]+\z/', $m[2])) 3636 displayOptionsSet($m[1], substr($m[2], 1), $on); 3637 else 3638 $ok = false; 3639 } else 3640 $this->conf->save_session($m[1], $m[3] ? intval(substr($m[3], 1)) : null); 3641 } 3642 return $ok; 3643 } 3644 3645 3646 // papers 3647 3648 function paper_set($pids, $options = null) { 3649 if (is_int($pids)) { 3650 $options["paperId"] = $pids; 3651 } else if (is_array($pids) 3652 && !is_associative_array($pids) 3653 && (!empty($pids) || $options !== null)) { 3654 $options["paperId"] = $pids; 3655 } else if (is_object($pids) && $pids instanceof SearchSelection) { 3656 $options["paperId"] = $pids->selection(); 3657 } else { 3658 $options = $pids; 3659 } 3660 return $this->conf->paper_set($this, $options); 3661 } 3662 3663 function hide_reviewer_identity_pids() { 3664 $pids = []; 3665 if (!$this->privChair || $this->conf->has_any_manager()) { 3666 $overrides = $this->add_overrides(Contact::OVERRIDE_CONFLICT); 3667 foreach ($this->paper_set([]) as $prow) { 3668 if (!$this->can_view_paper($prow) 3669 || !$this->can_view_review_assignment($prow, null) 3670 || !$this->can_view_review_identity($prow, null)) 3671 $pids[] = $prow->paperId; 3672 } 3673 $this->set_overrides($overrides); 3674 } 3675 return $pids; 3676 } 3677 3678 function paper_status_info(PaperInfo $row, $forceShow = null) { 3679 if ($row->timeWithdrawn > 0) { 3680 return array("pstat_with", "Withdrawn"); 3681 } else if ($row->outcome && $this->can_view_decision($row, $forceShow)) { 3682 $data = get(self::$status_info_cache, $row->outcome); 3683 if (!$data) { 3684 $decclass = ($row->outcome > 0 ? "pstat_decyes" : "pstat_decno"); 3685 3686 $decs = $this->conf->decision_map(); 3687 $decname = get($decs, $row->outcome); 3688 if ($decname) { 3689 $trdecname = preg_replace('/[^-.\w]/', '', $decname); 3690 if ($trdecname != "") 3691 $decclass .= " pstat_" . strtolower($trdecname); 3692 } else 3693 $decname = "Unknown decision #" . $row->outcome; 3694 3695 $data = self::$status_info_cache[$row->outcome] = array($decclass, $decname); 3696 } 3697 return $data; 3698 } else if ($row->timeSubmitted <= 0 && $row->paperStorageId == 1) { 3699 return array("pstat_noup", "No submission"); 3700 } else if ($row->timeSubmitted > 0) { 3701 return array("pstat_sub", "Submitted"); 3702 } else { 3703 return array("pstat_prog", "Not ready"); 3704 } 3705 } 3706 3707 3708 private function unassigned_review_token() { 3709 while (1) { 3710 $token = mt_rand(1, 2000000000); 3711 $result = $this->conf->qe("select reviewId from PaperReview where reviewToken=$token"); 3712 if (edb_nrows($result) == 0) 3713 return ", reviewToken=$token"; 3714 } 3715 } 3716 3717 function assign_review($pid, $reviewer_cid, $type, $extra = array()) { 3718 global $Now; 3719 $result = $this->conf->qe("select reviewId, reviewType, reviewRound, reviewModified, reviewToken, requestedBy, reviewSubmitted from PaperReview where paperId=? and contactId=?", $pid, $reviewer_cid); 3720 $rrow = edb_orow($result); 3721 Dbl::free($result); 3722 $reviewId = $rrow ? $rrow->reviewId : 0; 3723 $type = max((int) $type, 0); 3724 $oldtype = $rrow ? (int) $rrow->reviewType : 0; 3725 3726 // can't delete a review that's in progress 3727 if ($type <= 0 && $oldtype && $rrow->reviewModified > 1) { 3728 if ($oldtype >= REVIEW_SECONDARY) 3729 $type = REVIEW_PC; 3730 else 3731 return $reviewId; 3732 } 3733 // PC members always get PC reviews 3734 if ($type == REVIEW_EXTERNAL && $this->conf->pc_member_by_id($reviewer_cid)) 3735 $type = REVIEW_PC; 3736 3737 // change database 3738 if ($type && ($round = get($extra, "round_number")) === null) 3739 $round = $this->conf->assignment_round($type == REVIEW_EXTERNAL); 3740 if ($type && !$oldtype) { 3741 $qa = ""; 3742 if (get($extra, "mark_notify")) 3743 $qa .= ", timeRequestNotified=$Now"; 3744 if (get($extra, "token")) 3745 $qa .= $this->unassigned_review_token(); 3746 $new_requester_cid = $this->contactId; 3747 if (($new_requester = get($extra, "requester_contact"))) 3748 $new_requester_cid = $new_requester->contactId; 3749 $q = "insert into PaperReview set paperId=$pid, contactId=$reviewer_cid, reviewType=$type, reviewRound=$round, timeRequested=$Now$qa, requestedBy=$new_requester_cid"; 3750 } else if ($type && ($oldtype != $type || $rrow->reviewRound != $round)) { 3751 $q = "update PaperReview set reviewType=$type, reviewRound=$round"; 3752 if (!$rrow->reviewSubmitted) 3753 $q .= ", reviewNeedsSubmit=1"; 3754 $q .= " where reviewId=$reviewId"; 3755 } else if (!$type && $oldtype) 3756 $q = "delete from PaperReview where reviewId=$reviewId"; 3757 else 3758 return $reviewId; 3759 3760 if (!($result = $this->conf->qe_raw($q))) 3761 return false; 3762 3763 if ($type && !$oldtype) { 3764 $reviewId = $result->insert_id; 3765 $msg = "Review $reviewId added (" . ReviewForm::$revtype_names[$type] . ")"; 3766 } else if (!$type) { 3767 $msg = "Removed " . ReviewForm::$revtype_names[$oldtype] . " review"; 3768 $reviewId = 0; 3769 } else 3770 $msg = "Review $reviewId changed (" . ReviewForm::$revtype_names[$oldtype] . " to " . ReviewForm::$revtype_names[$type] . ")"; 3771 $this->conf->log_for($this, $reviewer_cid, $msg, $pid); 3772 3773 // on new review, update PaperReviewRefused, ReviewRequest, delegation 3774 if ($type && !$oldtype) { 3775 $this->conf->ql("delete from PaperReviewRefused where paperId=$pid and contactId=$reviewer_cid"); 3776 if (($req_email = get($extra, "requested_email"))) 3777 $this->conf->qe("delete from ReviewRequest where paperId=$pid and email=?", $req_email); 3778 if ($type < REVIEW_SECONDARY) 3779 $this->update_review_delegation($pid, $new_requester_cid, 1); 3780 if ($type >= REVIEW_PC 3781 && $this->conf->setting("pcrev_assigntime", 0) < $Now) 3782 $this->conf->save_setting("pcrev_assigntime", $Now); 3783 } else if (!$type) { 3784 if ($oldtype < REVIEW_SECONDARY && $rrow->requestedBy > 0) 3785 $this->update_review_delegation($pid, $rrow->requestedBy, -1); 3786 // Mark rev_tokens setting for future update by update_rev_tokens_setting 3787 if (get($rrow, "reviewToken")) 3788 $this->conf->settings["rev_tokens"] = -1; 3789 } else { 3790 if ($type == REVIEW_SECONDARY && $oldtype != REVIEW_SECONDARY 3791 && !$rrow->reviewSubmitted) 3792 $this->update_review_delegation($pid, $reviewer_cid, 0); 3793 } 3794 if ($type == REVIEW_META || $oldtype == REVIEW_META) 3795 $this->conf->update_metareviews_setting($type == REVIEW_META ? 1 : -1); 3796 3797 Contact::update_rights(); 3798 return $reviewId; 3799 } 3800 3801 function update_review_delegation($pid, $cid, $direction) { 3802 if ($direction > 0) { 3803 $this->conf->qe("update PaperReview set reviewNeedsSubmit=-1 where paperId=? and reviewType=" . REVIEW_SECONDARY . " and contactId=? and reviewSubmitted is null and reviewNeedsSubmit=1", $pid, $cid); 3804 } else { 3805 $row = Dbl::fetch_first_row($this->conf->qe("select sum(contactId=$cid and reviewType=" . REVIEW_SECONDARY . " and reviewSubmitted is null), sum(reviewType<" . REVIEW_SECONDARY . " and requestedBy=$cid and reviewSubmitted is not null), sum(reviewType<" . REVIEW_SECONDARY . " and requestedBy=$cid) from PaperReview where paperId=$pid")); 3806 if ($row && $row[0]) { 3807 $rns = $row[1] ? 0 : ($row[2] ? -1 : 1); 3808 if ($direction == 0 || $rns != 0) 3809 $this->conf->qe("update PaperReview set reviewNeedsSubmit=? where paperId=? and contactId=? and reviewSubmitted is null", $rns, $pid, $cid); 3810 } 3811 } 3812 } 3813 3814 function unsubmit_review_row($rrow) { 3815 $needsSubmit = 1; 3816 if ($rrow->reviewType == REVIEW_SECONDARY) { 3817 $row = Dbl::fetch_first_row($this->conf->qe("select count(reviewSubmitted), count(reviewId) from PaperReview where paperId=? and requestedBy=? and reviewType<" . REVIEW_SECONDARY, $rrow->paperId, $rrow->contactId)); 3818 if ($row && $row[0]) 3819 $needsSubmit = 0; 3820 else if ($row && $row[1]) 3821 $needsSubmit = -1; 3822 } 3823 return $this->conf->qe("update PaperReview set reviewSubmitted=null, reviewNeedsSubmit=? where paperId=? and reviewId=?", $needsSubmit, $rrow->paperId, $rrow->reviewId); 3824 } 3825} 3826