function proposeReview($email, $round)
{
    global $Conf, $Me, $Now, $prow, $rrows;
    $email = trim($email);
    $name = trim($_REQUEST["name"]);
    $reason = trim($_REQUEST["reason"]);
    $reqId = Contact::id_by_email($email);
    Dbl::qe_raw("lock tables PaperReview write, PaperReviewRefused write, ReviewRequest write, ContactInfo read, PaperConflict read");
    // NB caller unlocks tables on error
    if ($reqId > 0 && !($result = requestReviewChecks(htmlspecialchars($email), $reqId))) {
        return $result;
    }
    // add review request
    $result = Dbl::qe("insert into ReviewRequest set paperId={$prow->paperId},\n        name=?, email=?, requestedBy={$Me->contactId}, reason=?, reviewRound=?\n        on duplicate key update paperId=paperId", $name, $email, $reason, $round);
    // send confirmation email
    HotCRPMailer::send_manager("@proposereview", $prow, array("permissionContact" => $Me, "cc" => Text::user_email_to($Me), "requester_contact" => $Me, "reviewer_contact" => (object) array("fullName" => $name, "email" => $email), "reason" => $reason));
    // confirmation message
    $Conf->confirmMsg("Proposed that " . htmlspecialchars("{$name} <{$email}>") . " review paper #{$prow->paperId}.  The chair must approve this proposal for it to take effect.");
    Dbl::qx_raw("unlock tables");
    $Me->log_activity("Logged proposal for {$email} to review", $prow);
    return true;
}
 function save_review($req, $rrow, $prow, $contact, &$tf = null)
 {
     global $Conf, $Opt;
     $newsubmit = @$req["ready"] && !@$req["unready"] && (!$rrow || !$rrow->reviewSubmitted);
     $submit = $newsubmit || $rrow && $rrow->reviewSubmitted;
     $admin = $contact->allow_administer($prow);
     if (!$contact->timeReview($prow, $rrow) && (!isset($req['override']) || !$admin)) {
         return Conf::msg_error("The <a href='" . hoturl("deadlines") . "'>deadline</a> for entering this review has passed." . ($admin ? "  Select the “Override deadlines” checkbox and try again if you really want to override the deadline." : ""));
     }
     $q = array();
     $diff_view_score = VIEWSCORE_FALSE;
     $wc = 0;
     foreach ($this->forder as $field => $f) {
         if (isset($req[$field]) && (!$f->round_mask || $f->is_round_visible($rrow))) {
             $fval = $req[$field];
             if ($f->has_options) {
                 if ($f->parse_is_empty($fval)) {
                     $fval = 0;
                 } else {
                     if (!($fval = $f->parse_value($fval, false))) {
                         continue;
                     }
                 }
             } else {
                 $fval = rtrim($fval);
                 if ($fval != "") {
                     $fval .= "\n";
                 }
                 // Check for valid UTF-8; re-encode from Windows-1252 or Mac OS
                 $fval = convert_to_utf8($fval);
                 if ($f->include_word_count()) {
                     $wc += count_words($fval);
                 }
             }
             if ($rrow && strcmp($rrow->{$field}, $fval) != 0 && strcmp(cleannl($rrow->{$field}), cleannl($fval)) != 0) {
                 $diff_view_score = max($diff_view_score, $f->view_score);
             }
             $q[] = "{$field}='" . sqlq($fval) . "'";
         }
     }
     // get the current time
     $now = time();
     if ($rrow && $rrow->reviewModified && $rrow->reviewModified > $now) {
         $now = $rrow->reviewModified + 1;
     }
     // potentially assign review ordinal (requires table locking since
     // mySQL is stupid)
     $locked = false;
     if ($newsubmit) {
         $diff_view_score = max($diff_view_score, VIEWSCORE_AUTHOR);
         $q[] = "reviewSubmitted={$now}, reviewNeedsSubmit=0";
         if (!$rrow || !$rrow->reviewOrdinal) {
             $result = $Conf->qe("lock tables PaperReview write");
             if (!$result) {
                 return $result;
             }
             $locked = true;
             $result = $Conf->qe("select coalesce(max(reviewOrdinal), 0) from PaperReview where paperId={$prow->paperId} group by paperId");
             if ($result) {
                 $crow = edb_row($result);
                 $q[] = "reviewOrdinal=coalesce(reviewOrdinal, " . ($crow[0] + 1) . ")";
             }
             Dbl::free($result);
             $q[] = "timeDisplayed={$now}";
         }
     }
     // check whether used a review token
     $usedReviewToken = $contact->review_token_cid($prow, $rrow);
     // blind? reviewer type? edit version?
     $reviewBlind = $Conf->is_review_blind(!!@$req["blind"]);
     if ($rrow && $reviewBlind != $rrow->reviewBlind) {
         $diff_view_score = max($diff_view_score, VIEWSCORE_ADMINONLY);
     }
     $q[] = "reviewBlind=" . ($reviewBlind ? 1 : 0);
     if ($rrow && $rrow->reviewType == REVIEW_EXTERNAL && $contact->contactId == $rrow->contactId && $contact->isPC && !$usedReviewToken) {
         $q[] = "reviewType=" . REVIEW_PC;
     }
     if ($rrow && $diff_view_score > VIEWSCORE_FALSE && isset($req["version"]) && ctype_digit($req["version"]) && $req["version"] > defval($rrow, "reviewEditVersion")) {
         $q[] = "reviewEditVersion=" . ($req["version"] + 0);
     }
     if ($diff_view_score > VIEWSCORE_FALSE && $Conf->sversion >= 98) {
         $q[] = "reviewWordCount=" . $wc;
     }
     if (isset($req["reviewFormat"]) && $Conf->sversion >= 104 && @$Opt["formatInfo"]) {
         $fmt = null;
         foreach ($Opt["formatInfo"] as $k => $f) {
             if (@$f["name"] && strcasecmp($f["name"], $req["reviewFormat"]) == 0) {
                 $fmt = (int) $k;
             }
         }
         if (!$fmt && $req["reviewFormat"] && preg_match('/\\A(?:plain\\s*)?(?:text)?\\z/i', $f["reviewFormat"])) {
             $fmt = 0;
         }
         $q[] = "reviewFormat=" . ($fmt === null ? "null" : $fmt);
     }
     // notification
     $notification_bound = $now - 10800;
     $notify = $notify_author = false;
     if (!$rrow || $diff_view_score > VIEWSCORE_FALSE) {
         $q[] = "reviewModified=" . $now;
         // do not notify on updates within 3 hours
         if ($submit && $diff_view_score > VIEWSCORE_ADMINONLY) {
             if (!$rrow || !$rrow->reviewNotified || $rrow->reviewNotified < $notification_bound) {
                 $q[] = $notify = "reviewNotified=" . $now;
             }
             if ((!$rrow || !$rrow->reviewAuthorNotified || $rrow->reviewAuthorNotified < $notification_bound) && $diff_view_score >= VIEWSCORE_AUTHOR && Contact::can_some_author_view_submitted_review($prow)) {
                 $q[] = $notify_author = "reviewAuthorNotified=" . $now;
             }
         }
     }
     // actually affect database
     if ($rrow) {
         $result = $Conf->qe("update PaperReview set " . join(", ", $q) . " where reviewId={$rrow->reviewId}");
         $reviewId = $rrow->reviewId;
         $contactId = $rrow->contactId;
     } else {
         $result = Dbl::qe_raw("insert into PaperReview set paperId={$prow->paperId}, contactId={$contact->contactId}, reviewType=" . REVIEW_PC . ", requestedBy={$contact->contactId}, reviewRound=" . $Conf->current_round() . ", " . join(", ", $q));
         $reviewId = $result ? $result->insert_id : null;
         $contactId = $contact->contactId;
     }
     // unlock tables even if problem
     if ($locked) {
         $Conf->qe("unlock tables");
     }
     if (!$result) {
         return $result;
     }
     // update caches
     Contact::update_rights();
     // look up review ID
     if (!$reviewId) {
         return $reviewId;
     }
     $req['reviewId'] = $reviewId;
     // log updates -- but not if review token is used
     if (!$usedReviewToken && $diff_view_score > VIEWSCORE_FALSE) {
         $text = "Review {$reviewId} ";
         if ($rrow && $contact->contactId != $rrow->contactId) {
             $text .= "by {$rrow->email} ";
         }
         $text .= $newsubmit ? "submitted" : ($submit ? "updated" : "saved draft");
         $contact->log_activity($text, $prow);
     }
     // potentially email chair, reviewers, and authors
     if ($submit) {
         $rrow = $Conf->reviewRow(["reviewId" => $reviewId]);
     }
     if ($submit && ($notify || $notify_author) && $rrow) {
         $tmpl = $newsubmit ? "@reviewsubmit" : "@reviewupdate";
         $submitter = $contact;
         if ($contactId != $submitter->contactId) {
             $submitter = Contact::find_by_id($contactId);
         }
         // construct mail
         $this->mailer_info = array("template" => $tmpl, "rrow" => $rrow, "reviewer_contact" => $submitter, "reviewNumber" => $prow->paperId . unparseReviewOrdinal($rrow->reviewOrdinal), "check_function" => "HotCRPMailer::check_can_view_review", "diff_view_score" => $diff_view_score);
         $this->mailer_preps = array();
         if ($Conf->timeEmailChairAboutReview()) {
             HotCRPMailer::send_manager($tmpl, $prow, $this->mailer_info);
         }
         $prow->notify(WATCHTYPE_REVIEW, array($this, "review_watch_callback"), $contact);
         if (count($this->mailer_preps)) {
             HotCRPMailer::send_combined_preparations($this->mailer_preps);
         }
         unset($this->mailer_info, $this->mailer_preps);
     }
     // if external, forgive the requestor from finishing their review
     if ($rrow && $rrow->reviewType < REVIEW_SECONDARY && $rrow->requestedBy && $submit) {
         $Conf->q("update PaperReview set reviewNeedsSubmit=0 where paperId={$prow->paperId} and contactId={$rrow->requestedBy} and reviewType=" . REVIEW_SECONDARY . " and reviewSubmitted is null");
     }
     if ($tf !== null) {
         $what = "#{$prow->paperId}" . ($rrow && $rrow->reviewSubmitted ? unparseReviewOrdinal($rrow->reviewOrdinal) : "");
         if ($newsubmit) {
             $tf["newlySubmitted"][] = $what;
         } else {
             if ($diff_view_score > VIEWSCORE_FALSE && $submit) {
                 $tf["updated"][] = $what;
             } else {
                 if ($diff_view_score > VIEWSCORE_FALSE) {
                     $tf["savedDraft"][] = $what;
                 } else {
                     $tf["unchanged"][] = $what;
                 }
             }
         }
         if ($notify_author) {
             $tf["authorNotified"][] = $what;
         }
     }
     return $result;
 }