1<?php 2// a_preference.php -- HotCRP assignment helper classes 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5class Preference_AssignmentParser extends AssignmentParser { 6 function __construct() { 7 parent::__construct("pref"); 8 } 9 function load_state(AssignmentState $state) { 10 if (!$state->mark_type("pref", ["pid", "cid"], "Preference_Assigner::make")) 11 return; 12 $result = $state->conf->qe("select paperId, contactId, preference, expertise from PaperReviewPreference where paperId?a", $state->paper_ids()); 13 while (($row = edb_row($result))) 14 $state->load(["type" => "pref", "pid" => +$row[0], "cid" => +$row[1], "_pref" => +$row[2], "_exp" => self::make_exp($row[3])]); 15 Dbl::free($result); 16 } 17 function allow_paper(PaperInfo $prow, AssignmentState $state) { 18 return true; 19 } 20 function expand_any_user(PaperInfo $prow, &$req, AssignmentState $state) { 21 return array_filter($state->pc_users(), 22 function ($u) use ($prow) { 23 return $u->can_become_reviewer_ignore_conflict($prow); 24 }); 25 } 26 function expand_missing_user(PaperInfo $prow, &$req, AssignmentState $state) { 27 return $state->reviewer->isPC ? [$state->reviewer] : false; 28 } 29 function allow_contact(PaperInfo $prow, Contact $contact, &$req, AssignmentState $state) { 30 if (!$contact->contactId) 31 return false; 32 else if ($contact->contactId !== $state->user->contactId 33 && !$state->user->can_administer($prow)) 34 return "Can’t change other users’ preferences for #{$prow->paperId}."; 35 else if (!$contact->can_become_reviewer_ignore_conflict($prow)) { 36 if ($contact->contactId !== $state->user->contactId) 37 return Text::user_html_nolink($contact) . " can’t enter preferences for #{$prow->paperId}."; 38 else 39 return "Can’t enter preferences for #{$prow->paperId}."; 40 } else 41 return true; 42 } 43 static private function make_exp($exp) { 44 return $exp === null ? "N" : +$exp; 45 } 46 static function parse($str) { 47 if ($str === "" || strcasecmp($str, "none") == 0) 48 return [0, null]; 49 else if (is_numeric($str)) { 50 if ($str <= 1000000) 51 return [(int) round($str), null]; 52 else 53 return null; 54 } 55 56 $str = rtrim(preg_replace('{(?:\A\s*[\"\'`]\s*|\s*[\"\'`]\s*\z|\s+(?=[-+\d.xyz]))}i', "", $str)); 57 if ($str === "" || strcasecmp($str, "none") == 0 || strcasecmp($str, "n/a") == 0) 58 return [0, null]; 59 else if (strspn($str, "-") === strlen($str)) 60 return [-strlen($str), null]; 61 else if (strspn($str, "+") === strlen($str)) 62 return [strlen($str), null]; 63 else if (preg_match('{\A(?:--?(?=-[\d.])|\+(?=\+?[\d.])|)([-+]?(?:\d+(?:\.\d*)?|\.\d+)|)([xyz]?)(?:[-+]|)\z}i', $str, $m)) { 64 if ($m[1] === "") 65 $p = 0; 66 else if ($m[1] <= 1000000) 67 $p = (int) round($m[1]); 68 else 69 return null; 70 if ($m[2] === "") 71 $e = null; 72 else 73 $e = 9 - (ord($m[2]) & 15); 74 return [$p, $e]; 75 } else if (strcasecmp($str, "conflict") == 0) 76 return [-100, null]; 77 else { 78 $str2 = str_replace(["\xE2\x88\x92", "–", "—"], ["-", "-", "-"], $str); 79 return $str === $str2 ? null : self::parse($str2); 80 } 81 } 82 function apply(PaperInfo $prow, Contact $contact, &$req, AssignmentState $state) { 83 foreach (array("preference", "pref", "revpref") as $k) 84 if (($pref = get($req, $k)) !== null) 85 break; 86 if ($pref === null) 87 return "Missing preference."; 88 $ppref = self::parse($pref); 89 if ($ppref === null) { 90 if (preg_match('/([+-]?)\s*(\d+)\s*([xyz]?)/i', $pref, $m)) { 91 $msg = $state->conf->_("“%s” isn’t a valid preference. Did you mean “%s”?", htmlspecialchars($pref), $m[1] . $m[2] . strtoupper($m[3])); 92 } else { 93 $msg = $state->conf->_("“%s” isn’t a valid preference.", htmlspecialchars($pref)); 94 } 95 $state->user_error($msg); 96 return false; 97 } 98 99 foreach (array("expertise", "revexp") as $k) 100 if (($exp = get($req, $k)) !== null) 101 break; 102 if ($exp && ($exp = trim($exp)) !== "") { 103 if (($pexp = self::parse($exp)) === null || $pexp[0]) 104 return "Invalid expertise “" . htmlspecialchars($exp) . "”."; 105 $ppref[1] = $pexp[1]; 106 } 107 108 $state->remove(array("type" => "pref", "pid" => $prow->paperId, "cid" => $contact->contactId)); 109 if ($ppref[0] || $ppref[1] !== null) 110 $state->add(array("type" => "pref", "pid" => $prow->paperId, "cid" => $contact->contactId, "_pref" => $ppref[0], "_exp" => self::make_exp($ppref[1]))); 111 return true; 112 } 113} 114 115class Preference_Assigner extends Assigner { 116 function __construct(AssignmentItem $item, AssignmentState $state) { 117 parent::__construct($item, $state); 118 } 119 static function make(AssignmentItem $item, AssignmentState $state) { 120 return new Preference_Assigner($item, $state); 121 } 122 function unparse_description() { 123 return "preference"; 124 } 125 private function preference_data($before) { 126 $p = [$this->item->get($before, "_pref"), 127 $this->item->get($before, "_exp")]; 128 if ($p[1] === "N") 129 $p[1] = null; 130 return $p[0] || $p[1] !== null ? $p : null; 131 } 132 function unparse_display(AssignmentSet $aset) { 133 if (!$this->cid) 134 return "remove all preferences"; 135 $t = $aset->user->reviewer_html_for($this->contact); 136 if (($p = $this->preference_data(true))) 137 $t .= " <del>" . unparse_preference_span($p, true) . "</del>"; 138 if (($p = $this->preference_data(false))) 139 $t .= " <ins>" . unparse_preference_span($p, true) . "</ins>"; 140 return $t; 141 } 142 function unparse_csv(AssignmentSet $aset, AssignmentCsv $acsv) { 143 $p = $this->preference_data(false); 144 $pref = $p ? unparse_preference($p[0], $p[1]) : "none"; 145 return ["pid" => $this->pid, "action" => "preference", 146 "email" => $this->contact->email, "name" => $this->contact->name_text(), 147 "preference" => $pref]; 148 } 149 function add_locks(AssignmentSet $aset, &$locks) { 150 $locks["PaperReviewPreference"] = "write"; 151 } 152 function execute(AssignmentSet $aset) { 153 if (($p = $this->preference_data(false))) 154 $aset->stage_qe("insert into PaperReviewPreference 155 set paperId=?, contactId=?, preference=?, expertise=? 156 on duplicate key update preference=values(preference), expertise=values(expertise)", 157 $this->pid, $this->cid, $p[0], $p[1]); 158 else 159 $aset->stage_qe("delete from PaperReviewPreference where paperId=? and contactId=?", $this->pid, $this->cid); 160 } 161} 162