1<?php 2// api_requestreview.php -- HotCRP user-related API calls 3// Copyright (c) 2008-2018 Eddie Kohler; see LICENSE. 4 5class RequestReview_API { 6 static function requestreview($user, $qreq, $prow) { 7 if (($whyNot = $user->perm_request_review($prow, true))) 8 return new JsonResult(403, ["ok" => false, "error" => whyNotText($whyNot)]); 9 if (!isset($qreq->email)) 10 return new JsonResult(400, "Parameter error."); 11 $email = $qreq->email = trim($qreq->email); 12 if ($email === "" || $email === "newanonymous") 13 return self::requestreview_anonymous($user, $qreq, $prow); 14 15 $reviewer = $user->conf->user_by_email($email); 16 if (!$reviewer && ($email === "" || !validate_email($email))) 17 return self::error_result("email", "Invalid email address."); 18 19 $round = null; 20 if ((string) $qreq->round !== "" 21 && ($rname = $user->conf->sanitize_round_name($qreq->round)) !== false) 22 $round = (int) $user->conf->round_number($rname, false); 23 24 $name_args = Text::analyze_name_args([(object) ["firstName" => $qreq->firstName, "lastName" => $qreq->lastName, "name" => $qreq->name, "affiliation" => $qreq->affiliation, "email" => $email]]); 25 $reason = trim($qreq->reason); 26 27 // check proposal: 28 // - check existing review 29 if ($reviewer && $prow->review_of_user($reviewer)) 30 return self::error_result("email", htmlspecialchars($email) . " is already a reviewer."); 31 // - check existing request 32 $request = $user->conf->fetch_first_object("select * from ReviewRequest where paperId=? and email=?", $prow->paperId, $email); 33 if ($request && !$user->allow_administer($prow)) 34 return self::error_result("email", htmlspecialchars($email) . " is already a requested reviewer."); 35 // - check existing refusal 36 if ($reviewer 37 && ($refusal = $user->conf->fetch_first_object("select * from PaperReviewRefused where paperId=? and contactId=?", $prow->paperId, $reviewer->contactId)) 38 && (!$user->can_administer($prow) || !$qreq->override)) { 39 $errf = ["email" => true]; 40 $msg = htmlspecialchars($email) . " has already declined to review this paper."; 41 if ((string) $refusal->reason !== "") 42 $msg .= " They offered this reason: <blockquote>" . htmlspecialchars($refusal->reason) . "</blockquote>"; 43 if ($user->allow_administer($prow)) 44 $errf["override"] = true; 45 return self::error_result($errf, $msg); 46 } 47 // - check conflict 48 if ($reviewer 49 && ($prow->has_conflict($reviewer) 50 || ($reviewer->isPC && !$reviewer->can_accept_review_assignment($prow)))) 51 return self::error_result("email", htmlspecialchars($email) . " cannot be asked to review this paper."); 52 53 // check for potential conflict 54 $xreviewer = $reviewer; 55 if (!$xreviewer) 56 $xreviewer = $user->conf->contactdb_user_by_email($email); 57 if (!$xreviewer) 58 $xreviewer = new Contact($name_args, $user->conf); 59 $potconflict = $prow->potential_conflict_html($xreviewer); 60 if ($potconflict 61 && $user->can_administer($prow) 62 && !$qreq->override) 63 return self::error_result("override", "<p>" . Text::user_html($xreviewer) . " has a potential conflict with this paper, so the request has not been made.</p>" . $potconflict[1]); 64 65 // check whether to make a proposal 66 $extrev_chairreq = $user->conf->setting("extrev_chairreq"); 67 if (!$user->can_administer($prow) 68 && ($extrev_chairreq === 1 69 || ($extrev_chairreq === 2 && $potconflict))) { 70 $result = Dbl::qe("insert into ReviewRequest set paperId=?, email=?, firstName=?, lastName=?, affiliation=?, requestedBy=?, reason=?, reviewRound=? on duplicate key update paperId=paperId", 71 $prow->paperId, $email, $xreviewer->firstName, $xreviewer->lastName, 72 $xreviewer->affiliation, $user->contactId, $reason, $round); 73 if ($extrev_chairreq === 2) { 74 $msg = "<p>" . Text::user_html($xreviewer) . " has a potential conflict with this submission, so an administrator must approve your proposed external review before it can take effect.</p>"; 75 if ($user->can_view_authors($prow)) 76 $msg .= $potconflict[1]; 77 } else 78 $msg = "Proposed an external review from " . Text::user_html($xreviewer) . ". An administrator must approve this proposal for it to take effect."; 79 $user->log_activity("Logged proposal for $email to review", $prow); 80 return ["ok" => true, "action" => "propose", "response" => $msg]; 81 } 82 83 // if we get here, we will (try to) assign a review 84 85 // create account 86 if (!$reviewer) 87 $reviewer = Contact::create($user->conf, $user, $xreviewer); 88 if (!$reviewer) 89 return new JsonResult(400, "Error while creating account."); 90 91 // check requester 92 $requester = null; 93 if ($request 94 && $user->can_administer($prow)) 95 $requester = $user->conf->cached_user_by_id($request->requestedBy); 96 $requester = $requester ? : $user; 97 98 // assign review 99 $user->assign_review($prow->paperId, $reviewer->contactId, REVIEW_EXTERNAL, 100 ["mark_notify" => true, 101 "requester_contact" => $requester, 102 "requested_email" => $reviewer->email, 103 "round_number" => $round]); 104 105 // send confirmation mail 106 HotCRPMailer::send_to($reviewer, "@requestreview", $prow, 107 ["requester_contact" => $requester, 108 "reason" => $reason]); 109 110 return ["ok" => true, "action" => "request", "response" => "Requested an external review from " . Text::user_html($reviewer) . "."]; 111 } 112 113 static function requestreview_anonymous($user, $qreq, $prow) { 114 if (trim((string) $qreq->firstName) !== "" 115 || trim((string) $qreq->lastName) !== "") 116 return new JsonResult(400, "An email address is required to request a review."); 117 if (!$user->allow_administer($prow)) 118 return new JsonResult(403, "Only administrators can request anonymous reviews."); 119 $aset = new AssignmentSet($user, true); 120 $aset->enable_papers($prow); 121 $aset->parse("paper,action,user\n{$prow->paperId},review,newanonymous\n"); 122 if ($aset->execute()) { 123 $aset_csv = $aset->unparse_csv(); 124 assert(count($aset_csv->data) === 1); 125 return ["ok" => true, "action" => "token", "review_token" => $aset_csv->data[0]["review_token"]]; 126 } else 127 return new JsonResult(400, ["ok" => false, "error" => $aset->errors_div_html()]); 128 } 129 130 static function error_result($errf, $message) { 131 if (is_string($errf)) 132 $errf = [$errf => true]; 133 return new JsonResult(400, ["ok" => false, "error" => $message, "errf" => $errf]); 134 } 135} 136