function run(Contact $user, $qreq, $ssel)
 {
     global $Conf;
     $result = Dbl::qe_raw($Conf->paperQuery($user, ["paperId" => $ssel->selection()]));
     $downloads = [];
     $opt = PaperOption::find_document($this->dt);
     while ($row = PaperInfo::fetch($result, $user)) {
         if ($whyNot = $user->perm_view_paper_option($row, $opt, true)) {
             Conf::msg_error(whyNotText($whyNot, "view"));
         } else {
             $downloads[] = $row->paperId;
         }
     }
     if (count($downloads)) {
         session_write_close();
         // it can take a while to generate the download
         if ($Conf->downloadPaper($downloads, true, $this->dt)) {
             exit;
         }
     }
     // XXX how to return errors?
 }
 private function _rows($field_list)
 {
     global $Conf;
     if (!$field_list) {
         return null;
     }
     // prepare query text
     $this->qopts["scores"] = array_keys($this->qopts["scores"]);
     if (empty($this->qopts["scores"])) {
         unset($this->qopts["scores"]);
     }
     $pq = $Conf->paperQuery($this->contact, $this->qopts);
     // make query
     $result = Dbl::qe_raw($pq);
     if (!$result) {
         return null;
     }
     // fetch rows
     $rows = array();
     while ($row = PaperInfo::fetch($result, $this->contact)) {
         $rows[$row->paperId] = $row;
     }
     // prepare review query (see also search > getfn == "reviewers")
     $this->review_list = array();
     if (isset($this->qopts["reviewList"]) && !empty($rows)) {
         $result = Dbl::qe("select Paper.paperId, reviewId, reviewType,\n                reviewSubmitted, reviewModified, reviewNeedsSubmit, reviewRound,\n                reviewOrdinal,\n                PaperReview.contactId, lastName, firstName, email\n                from Paper\n                join PaperReview using (paperId)\n                join ContactInfo on (PaperReview.contactId=ContactInfo.contactId)\n                where paperId?a", array_keys($rows));
         while ($row = edb_orow($result)) {
             Contact::set_sorter($row);
             $this->review_list[$row->paperId][] = $row;
         }
         foreach ($this->review_list as &$revlist) {
             usort($revlist, "PaperList::review_list_compar");
         }
         unset($revlist);
     }
     // prepare PC topic interests
     if (isset($this->qopts["allReviewerPreference"])) {
         $ord = 0;
         $pcm = pcMembers();
         foreach ($pcm as $pc) {
             $pc->prefOrdinal = sprintf("-0.%04d", $ord++);
             $pc->topicInterest = array();
         }
         $result = Dbl::qe("select contactId, topicId, " . $Conf->query_topic_interest() . " from TopicInterest");
         while ($row = edb_row($result)) {
             $pcm[$row[0]]->topicInterest[$row[1]] = $row[2];
         }
     }
     // analyze rows (usually noop)
     foreach ($field_list as $fdef) {
         $fdef->analyze($this, $rows);
     }
     // sort rows
     if (!empty($this->sorters)) {
         $rows = $this->_sort($rows);
         if (isset($this->qopts["allReviewScores"])) {
             $this->_sortReviewOrdinal($rows);
         }
     }
     // set `any->optID`
     if ($nopts = PaperOption::count_option_list()) {
         foreach ($rows as $prow) {
             foreach ($prow->options() as $o) {
                 if (!$this->any["opt{$o->id}"] && $this->contact->can_view_paper_option($prow, $o->option)) {
                     $this->any["opt{$o->id}"] = true;
                     --$nopts;
                 }
             }
             if (!$nopts) {
                 break;
             }
         }
     }
     Dbl::free($result);
     return $rows;
 }
 function example_searches()
 {
     $x = parent::example_searches();
     $x["attachment-count"] = array("{$this->abbr}:>2", $this);
     $x["attachment-filename"] = array("{$this->abbr}:*.gif", $this);
     return $x;
 }
Esempio n. 4
0
 function can_view_some_paper_option(PaperOption $opt)
 {
     if ($opt->has_document() && !$this->can_view_some_pdf() || $opt->final && !$this->can_view_some_decision()) {
         return false;
     }
     $oview = $opt->visibility;
     return $this->is_author() || $oview == "admin" && $this->is_manager() || (!$oview || $oview == "rev") && $this->is_reviewer() || $oview == "nonblind" && $this->can_view_some_authors();
 }
Esempio n. 5
0
function document_download()
{
    global $Conf, $Me, $Opt;
    $documentType = HotCRPDocument::parse_dtype(@$_REQUEST["dt"]);
    if ($documentType === null) {
        $documentType = @$_REQUEST["final"] ? DTYPE_FINAL : DTYPE_SUBMISSION;
    }
    $attachment_filename = false;
    $docid = null;
    if (isset($_REQUEST["p"])) {
        $paperId = cvtint(@$_REQUEST["p"]);
    } else {
        if (isset($_REQUEST["paperId"])) {
            $paperId = cvtint(@$_REQUEST["paperId"]);
        } else {
            $s = $orig_s = preg_replace(',\\A/*,', "", Navigation::path());
            $documentType = $dtname = null;
            if (str_starts_with($s, $Opt["downloadPrefix"])) {
                $s = substr($s, strlen($Opt["downloadPrefix"]));
            }
            if (preg_match(',\\Ap(?:aper)?(\\d+)/+(.*)\\z,', $s, $m)) {
                $paperId = intval($m[1]);
                if (preg_match(',\\A([^/]+)\\.[^/]+\\z,', $m[2], $mm)) {
                    $dtname = $mm[1];
                } else {
                    if (preg_match(',\\A([^/]+)/+(.*)\\z,', $m[2], $mm)) {
                        list($dtype, $attachment_filename) = array($m[1], $m[2]);
                    }
                }
            } else {
                if (preg_match(',\\A(?:paper)?(\\d+)-?([-A-Za-z0-9_]*)(?:\\.[^/]+|/+(.*))\\z,', $s, $m)) {
                    list($paperId, $dtname, $attachment_filename) = array(intval($m[1]), $m[2], @$m[3]);
                } else {
                    if (preg_match(',\\A([A-Za-z_][-A-Za-z0-9_]*?)?-?(\\d+)(?:\\.[^/]+|/+(.*))\\z,', $s, $m)) {
                        list($paperId, $dtname, $attachment_filename) = array(intval($m[2]), $m[1], @$m[3]);
                    }
                }
            }
            if ($dtname !== null) {
                $documentType = HotCRPDocument::parse_dtype($dtname ?: "paper");
            }
            if ($documentType !== null && $attachment_filename) {
                $o = PaperOption::find($documentType);
                if (!$o || $o->type != "attachments") {
                    $documentType = null;
                }
            }
        }
    }
    if ($documentType === null) {
        document_error("404 Not Found", "Unknown document “" . htmlspecialchars($orig_s) . "”.");
    }
    $prow = $Conf->paperRow($paperId, $Me, $whyNot);
    if (!$prow) {
        document_error("404 Not Found", whyNotText($whyNot, "view"));
    } else {
        if ($whyNot = $Me->perm_view_pdf($prow)) {
            document_error("403 Forbidden", whyNotText($whyNot, "view"));
        } else {
            if ($documentType > 0 && !$Me->can_view_paper_option($prow, $documentType, true)) {
                document_error("403 Forbidden", "You don’t have permission to view this document.");
            }
        }
    }
    if ($attachment_filename) {
        $oa = $prow->option($documentType);
        foreach ($oa ? $oa->documents($prow) : array() as $doc) {
            if ($doc->unique_filename == $attachment_filename) {
                $docid = $doc;
            }
        }
        if (!$docid) {
            document_error("404 Not Found", "No such attachment “" . htmlspecialchars($orig_s) . "”.");
        }
    }
    // Actually download paper.
    session_write_close();
    // to allow concurrent clicks
    if ($Conf->downloadPaper($prow, cvtint(@$_REQUEST["save"]) > 0, $documentType, $docid)) {
        exit;
    }
    document_error("500 Server Error", null);
}
 public function save($sv, $si)
 {
     global $Conf;
     $new_opts = $this->stashed_options;
     $current_opts = PaperOption::nonfixed_option_list();
     $this->option_clean_form_positions($new_opts, $current_opts);
     $newj = (object) array();
     uasort($new_opts, array("PaperOption", "compare"));
     $nextid = max($Conf->setting("next_optionid", 1), $Conf->setting("options", 1));
     foreach ($new_opts as $id => $o) {
         $newj->{$id} = $o->unparse();
         $nextid = max($nextid, $id + 1);
     }
     $sv->save("next_optionid", null);
     $sv->save("options", count($newj) ? json_encode($newj) : null);
     $deleted_ids = array();
     foreach ($current_opts as $id => $o) {
         if (!get($new_opts, $id)) {
             $deleted_ids[] = $id;
         }
     }
     if (count($deleted_ids)) {
         $Conf->qe("delete from PaperOption where optionId in (" . join(",", $deleted_ids) . ")");
     }
     // invalidate cached option list
     PaperOption::invalidate_option_list();
 }
 public function all_options()
 {
     if ($this->_all_option_array === null) {
         $this->_all_option_array = PaperOption::parse_paper_options($this, true);
     }
     return $this->_all_option_array;
 }
 public function make_column($name, $errors)
 {
     $p = strpos($name, ":") ?: -1;
     $opts = PaperOption::search(substr($name, $p + 1));
     if (count($opts) == 1) {
         reset($opts);
         $opt = current($opts);
         if ($opt->display() >= 0) {
             return self::_make_column($opt);
         }
         self::make_column_error($errors, "Option “" . htmlspecialchars(substr($name, $p + 1)) . "” can’t be displayed.");
     } else {
         if ($p > 0) {
             self::make_column_error($errors, "No such option “" . htmlspecialchars(substr($name, $p + 1)) . "”.", 1);
         }
     }
     return null;
 }
 function save_paper_json($pj)
 {
     global $Conf, $Now;
     assert(!$this->hide_docids);
     $paperid = null;
     if (isset($pj->pid) && is_int($pj->pid) && $pj->pid > 0) {
         $paperid = $pj->pid;
     } else {
         if (!isset($pj->pid) && isset($pj->id) && is_int($pj->id) && $pj->id > 0) {
             $paperid = $pj->id;
         } else {
             if (isset($pj->pid) || isset($pj->id)) {
                 $key = isset($pj->pid) ? "pid" : "id";
                 $this->set_error_html($key, "Format error [{$key}]");
                 return false;
             }
         }
     }
     if (get($pj, "error") || get($pj, "error_html")) {
         $this->set_error_html("error", "Refusing to save paper with error");
         return false;
     }
     $this->prow = $old_pj = null;
     $this->paperid = $paperid ?: -1;
     if ($paperid) {
         $this->prow = $Conf->paperRow(["paperId" => $paperid, "topics" => true, "options" => true], $this->contact);
     }
     if ($this->prow) {
         $old_pj = $this->paper_json($this->prow, ["forceShow" => true]);
     }
     if ($pj && $old_pj && $paperid != $old_pj->pid) {
         $this->set_error_html("pid", "Saving paper with different ID");
         return false;
     }
     $this->normalize($pj, $old_pj);
     if ($old_pj) {
         $this->normalize($old_pj, null);
     }
     if ($this->nerrors) {
         return false;
     }
     $this->check_invariants($pj, $old_pj);
     // store documents (options already stored)
     if (isset($pj->submission) && $pj->submission) {
         $this->upload_document($pj->submission, PaperOption::find_document(DTYPE_SUBMISSION));
     }
     if (isset($pj->final) && $pj->final) {
         $this->upload_document($pj->final, PaperOption::find_document(DTYPE_FINAL));
     }
     // create contacts
     foreach (self::contacts_array($pj) as $c) {
         $c->only_if_contactdb = !get($c, "contact");
         $c->disabled = !!$this->disable_users;
         if (!Contact::create($c, !$this->no_email) && get($c, "contact")) {
             $this->set_error_html("contacts", "Could not create an account for contact " . Text::user_html($c) . ".");
         }
     }
     // catch errors
     if ($this->nerrors) {
         return false;
     }
     // update Paper table
     $q = array();
     foreach (array("title", "abstract", "collaborators") as $k) {
         $v = convert_to_utf8((string) get($pj, $k));
         if (!$old_pj || get($pj, $k) !== null && $v !== (string) get($old_pj, $k)) {
             $q[] = "{$k}='" . sqlq($v) . "'";
         }
     }
     if (!$old_pj || get($pj, "authors") !== null) {
         $autext = convert_to_utf8(self::author_information($pj));
         $old_autext = self::author_information($old_pj);
         if ($autext !== $old_autext || !$old_pj) {
             $q[] = "authorInformation='" . sqlq($autext) . "'";
         }
     }
     if ($Conf->submission_blindness() == Conf::BLIND_OPTIONAL && (!$old_pj || get($pj, "nonblind") !== null && !$pj->nonblind != !$old_pj->nonblind)) {
         $q[] = "blind=" . (get($pj, "nonblind") ? 0 : 1);
     }
     if (!$old_pj || get($pj, "submission") !== null) {
         $new_id = get($pj, "submission") ? $pj->submission->docid : 1;
         $old_id = $old_pj && get($old_pj, "submission") ? $old_pj->submission->docid : 1;
         if (!$old_pj || $new_id != $old_id) {
             $q[] = "paperStorageId={$new_id}";
         }
     }
     if (!$old_pj || get($pj, "final") !== null) {
         $new_id = get($pj, "final") ? $pj->final->docid : 0;
         $old_id = $old_pj && get($old_pj, "final") ? $old_pj->final->docid : 0;
         if (!$old_pj || $new_id != $old_id) {
             $q[] = "finalPaperStorageId={$new_id}";
         }
     }
     if (get($pj, "withdrawn") !== null || get($pj, "submitted") !== null || get($pj, "draft") !== null) {
         if (get($pj, "submitted") !== null) {
             $submitted = $pj->submitted;
         } else {
             if (get($pj, "draft") !== null) {
                 $submitted = !$pj->draft;
             } else {
                 if ($old_pj) {
                     $submitted = get($old_pj, "submitted_at") > 0;
                 } else {
                     $submitted = false;
                 }
             }
         }
         if (get($pj, "withdrawn")) {
             if (!$old_pj || !get($old_pj, "withdrawn")) {
                 $q[] = "timeWithdrawn=" . (get($pj, "withdrawn_at") ?: $Now);
                 $q[] = "timeSubmitted=" . ($submitted ? -100 : 0);
             } else {
                 if (get($old_pj, "submitted_at") > 0 !== $submitted) {
                     $q[] = "timeSubmitted=" . ($submitted ? -100 : 0);
                 }
             }
         } else {
             if ($submitted) {
                 if (!$old_pj || !get($old_pj, "submitted")) {
                     $q[] = "timeSubmitted=" . (get($pj, "submitted_at") ?: $Now);
                 }
                 if ($old_pj && get($old_pj, "withdrawn")) {
                     $q[] = "timeWithdrawn=0";
                 }
             } else {
                 if ($old_pj && (get($old_pj, "withdrawn") || get($old_pj, "submitted"))) {
                     $q[] = "timeSubmitted=0";
                     $q[] = "timeWithdrawn=0";
                 }
             }
         }
     }
     if (get($pj, "final_submitted") !== null) {
         if ($pj->final_submitted) {
             $time = get($pj, "final_submitted_at") ?: $Now;
         } else {
             $time = 0;
         }
         if (!$old_pj || get($old_pj, "final_submitted_at") != $time) {
             $q[] = "timeFinalSubmitted={$time}";
         }
     }
     if (!empty($q)) {
         if ($Conf->submission_blindness() == Conf::BLIND_NEVER) {
             $q[] = "blind=0";
         } else {
             if ($Conf->submission_blindness() != Conf::BLIND_OPTIONAL) {
                 $q[] = "blind=1";
             }
         }
         $joindoc = $old_joindoc = null;
         if (get($pj, "final")) {
             $joindoc = $pj->final;
             $old_joindoc = $old_pj ? get($old_pj, "final") : null;
         } else {
             if (get($pj, "submission")) {
                 $joindoc = $pj->submission;
                 $old_joindoc = $old_pj ? get($old_pj, "submission") : null;
             }
         }
         if ($joindoc && (!$old_joindoc || $old_joindoc->docid != $joindoc->docid) && get($joindoc, "size") && get($joindoc, "timestamp")) {
             $q[] = "size=" . $joindoc->size;
             $q[] = "mimetype='" . sqlq($joindoc->mimetype) . "'";
             $q[] = "sha1='" . sqlq($joindoc->sha1) . "'";
             $q[] = "timestamp=" . $joindoc->timestamp;
         } else {
             if (!$joindoc) {
                 $q[] = "size=0,mimetype='',sha1='',timestamp=0";
             }
         }
         if ($paperid) {
             $result = Dbl::qe_raw("update Paper set " . join(",", $q) . " where paperId={$paperid}");
             if ($result && $result->affected_rows === 0 && edb_nrows(Dbl::qe_raw("select paperId from Paper where paperId={$paperid}")) === 0) {
                 $result = Dbl::qe_raw("insert into Paper set paperId={$paperid}, " . join(",", $q));
             }
         } else {
             $result = Dbl::qe_raw("insert into Paper set " . join(",", $q));
             if (!$result || !($paperid = $pj->pid = $result->insert_id)) {
                 return $this->set_error_html(false, "Could not create paper.");
             }
             if (!empty($this->uploaded_documents)) {
                 Dbl::qe_raw("update PaperStorage set paperId={$paperid} where paperStorageId in (" . join(",", $this->uploaded_documents) . ")");
             }
         }
         // maybe update `papersub` settings
         $is_submitted = !get($pj, "withdrawn") && get($pj, "submitted");
         $was_submitted = $old_pj && !get($old_pj, "withdrawn") && get($old_pj, "submitted");
         if ($is_submitted != $was_submitted) {
             $Conf->update_papersub_setting($is_submitted);
         }
     }
     // update PaperTopics
     if (get($pj, "topics")) {
         $topics = self::topics_sql($pj, $paperid);
         $old_topics = self::topics_sql($old_pj, $paperid);
         if ($topics !== $old_topics) {
             $result = Dbl::qe_raw("delete from PaperTopic where paperId={$paperid}");
             if ($topics) {
                 $result = Dbl::qe_raw("insert into PaperTopic (topicId,paperId) values {$topics}");
             }
         }
     }
     // update PaperOption
     if (get($pj, "options")) {
         $options = convert_to_utf8(self::options_sql($pj, $paperid));
         $old_options = self::options_sql($old_pj, $paperid);
         if ($options !== $old_options) {
             $result = Dbl::qe("delete from PaperOption where paperId={$paperid} and optionId?a", array_keys($pj->parsed_options));
             if ($options) {
                 $result = Dbl::qe_raw("insert into PaperOption (paperId,optionId,value,data) values {$options}");
             }
         }
     }
     // update PaperConflict
     $conflict = $this->conflicts_array($pj, $old_pj);
     $old_conflict = $this->conflicts_array($old_pj, null);
     if (join(",", array_keys($conflict)) !== join(",", array_keys($old_conflict)) || join(",", array_values($conflict)) !== join(",", array_values($old_conflict))) {
         $q = array();
         foreach ($conflict as $email => $type) {
             $q[] = "'" . sqlq($email) . "'";
         }
         $ins = array();
         if (!empty($q)) {
             $result = Dbl::qe_raw("select contactId, email from ContactInfo where email in (" . join(",", $q) . ")");
             while ($row = edb_row($result)) {
                 $ins[] = "({$paperid},{$row['0']}," . $conflict[strtolower($row[1])] . ")";
             }
         }
         $result = Dbl::qe_raw("delete from PaperConflict where paperId={$paperid}");
         if (!empty($ins)) {
             $result = Dbl::qe_raw("insert into PaperConflict (paperId,contactId,conflictType) values " . join(",", $ins));
         }
     }
     return $paperid;
 }
Esempio n. 10
0
function paperOptions($id = null)
{
    global $Conf;
    if ($Conf->setting("paperOption") <= 0 || $Conf->sversion <= 0) {
        return array();
    }
    $svar = defval($_SESSION, "paperOption", null);
    if (!$svar || !is_array($svar) || count($svar) < 3 || $svar[2] < 2 || $svar[0] < $Conf->setting("paperOption")) {
        $opt = array();
        $result = $Conf->q("select * from OptionType order by sortOrder, optionName");
        $order = 0;
        while ($row = edb_orow($result)) {
            // begin backwards compatibility to old schema versions
            if (!isset($row->optionValues)) {
                $row->optionValues = "";
            }
            if (!isset($row->type) && $row->optionValues == "i") {
                $row->type = PaperOption::T_NUMERIC;
            } else {
                if (!isset($row->type)) {
                    $row->type = $row->optionValues ? PaperOption::T_SELECTOR : PaperOption::T_CHECKBOX;
                }
            }
            // end backwards compatibility to old schema versions
            $row->optionAbbrev = preg_replace("/-+\$/", "", preg_replace("/[^a-z0-9_]+/", "-", strtolower($row->optionName)));
            if ($row->optionAbbrev == "paper" || $row->optionAbbrev == "submission" || $row->optionAbbrev == "final" || ctype_digit($row->optionAbbrev)) {
                $row->optionAbbrev = "opt" . $row->optionId;
            }
            $row->sortOrder = $order++;
            if (!isset($row->displayType)) {
                $row->displayType = PaperOption::DT_NORMAL;
            }
            if ($row->type == PaperOption::T_FINALPDF) {
                $row->displayType = PaperOption::DT_SUBMISSION;
            }
            $row->isDocument = PaperOption::type_is_document($row->type);
            $row->isFinal = PaperOption::type_is_final($row->type);
            $opt[$row->optionId] = $row;
        }
        $_SESSION["paperOption"] = $svar = array($Conf->setting("paperOption"), $opt, 2);
    }
    return $id ? defval($svar[1], $id, null) : $svar[1];
}
 public function diffs(&$diffs, $pj, $opj)
 {
     global $Conf;
     if (!$opj) {
         $diffs["new"] = true;
         return;
     }
     foreach (array("title", "abstract", "collaborators") as $k) {
         if (get_s($pj, $k) !== get_s($opj, $k)) {
             $diffs[$k] = true;
         }
     }
     if (!$this->same_authors($pj, $opj)) {
         $diffs["authors"] = true;
     }
     if (json_encode(get($pj, "topics") ?: (object) array()) !== json_encode(get($opj, "topics") ?: (object) array())) {
         $diffs["topics"] = true;
     }
     $pjopt = get($pj, "options", (object) []);
     $opjopt = get($opj, "options", (object) []);
     foreach (PaperOption::option_list() as $o) {
         $oabbr = $o->abbr;
         if (!get($pjopt, $oabbr) != !get($opjopt, $oabbr) || get($pjopt, $oabbr) && json_encode($pjopt->{$oabbr}) !== json_encode($opjopt->{$oabbr})) {
             $diffs["options"] = true;
             break;
         }
     }
     if ($Conf->subBlindOptional() && !get($pj, "nonblind") !== !get($opj, "nonblind")) {
         $diffs["anonymity"] = true;
     }
     if (json_encode(get($pj, "pc_conflicts")) !== json_encode(get($opj, "pc_conflicts"))) {
         $diffs["PC conflicts"] = true;
     }
     if (json_encode(get($pj, "submission")) !== json_encode(get($opj, "submission"))) {
         $diffs["submission"] = true;
     }
     if (json_encode(get($pj, "final")) !== json_encode(get($opj, "final"))) {
         $diffs["final copy"] = true;
     }
 }
 public function mimetypes($doc = null, $docinfo = null)
 {
     global $Opt;
     if ($this->dtype > 0 && !$this->option) {
         return null;
     }
     $otype = $this->option ? $this->option->type : "pdf";
     $mimetypes = array();
     if (PaperOption::type_takes_pdf($otype)) {
         $mimetypes[] = Mimetype::lookup(".pdf");
     }
     if (!$this->option && !defval($Opt, "disablePS")) {
         $mimetypes[] = Mimetype::lookup(".ps");
     }
     if ($otype == "slides") {
         $mimetypes[] = Mimetype::lookup(".ppt");
         $mimetypes[] = Mimetype::lookup(".pptx");
     }
     if ($otype == "video") {
         $mimetypes[] = Mimetype::lookup(".mp4");
         $mimetypes[] = Mimetype::lookup(".avi");
     }
     return $mimetypes;
 }
function updateSchema($Conf)
{
    global $Opt, $OK;
    // avoid error message abut timezone, set to $Opt
    // (which might be overridden by database values later)
    if (function_exists("date_default_timezone_set") && isset($Opt["timezone"]) && $Opt["timezone"]) {
        date_default_timezone_set($Opt["timezone"]);
    }
    while (($result = Dbl::ql("insert into Settings set name='__schema_lock', value=1 on duplicate key update value=1")) && $result->affected_rows == 0) {
        time_nanosleep(0, 200000000);
    }
    $Conf->update_schema_version(null);
    error_log($Opt["dbName"] . ": updating schema from version " . $Conf->sversion);
    if ($Conf->sversion == 6 && Dbl::ql("alter table ReviewRequest add `reason` text")) {
        $Conf->update_schema_version(7);
    }
    if ($Conf->sversion == 7 && Dbl::ql("alter table PaperReview add `textField7` mediumtext NOT NULL") && Dbl::ql("alter table PaperReview add `textField8` mediumtext NOT NULL") && Dbl::ql("insert into ReviewFormField set fieldName='textField7', shortName='Additional text field'") && Dbl::ql("insert into ReviewFormField set fieldName='textField8', shortName='Additional text field'")) {
        $Conf->update_schema_version(8);
    }
    if ($Conf->sversion == 8 && Dbl::ql("alter table ReviewFormField add `levelChar` tinyint(1) NOT NULL default '0'") && Dbl::ql("alter table PaperReviewArchive add `textField7` mediumtext NOT NULL") && Dbl::ql("alter table PaperReviewArchive add `textField8` mediumtext NOT NULL")) {
        $Conf->update_schema_version(9);
    }
    if ($Conf->sversion == 9 && Dbl::ql("alter table Paper add `sha1` varbinary(20) NOT NULL default ''")) {
        $Conf->update_schema_version(10);
    }
    if ($Conf->sversion == 10 && Dbl::ql("alter table PaperReview add `reviewRound` tinyint(1) NOT NULL default '0'") && Dbl::ql("alter table PaperReviewArchive add `reviewRound` tinyint(1) NOT NULL default '0'") && Dbl::ql("alter table PaperReview add key `reviewRound` (`reviewRound`)") && $Conf->update_schema_version(11)) {
        if (count($Conf->round_list()) > 1) {
            // update review rounds (XXX locking)
            $result = Dbl::ql("select paperId, tag from PaperTag where tag like '%~%'");
            $rrs = array();
            while ($row = edb_row($result)) {
                list($contact, $round) = explode("~", $row[1]);
                if ($round = array_search($round, $Conf->round_list())) {
                    if (!isset($rrs[$round])) {
                        $rrs[$round] = array();
                    }
                    $rrs[$round][] = "(contactId={$contact} and paperId={$row['0']})";
                }
            }
            foreach ($rrs as $round => $pairs) {
                $q = "update PaperReview set reviewRound={$round} where " . join(" or ", $pairs);
                Dbl::ql($q);
            }
            $x = trim(preg_replace('/(\\S+)\\s*/', "tag like '%~\$1' or ", $Conf->setting_data("tag_rounds")));
            Dbl::ql("delete from PaperTag where " . substr($x, 0, strlen($x) - 3));
        }
    }
    if ($Conf->sversion == 11 && Dbl::ql("create table `ReviewRating` (\n  `reviewId` int(11) NOT NULL,\n  `contactId` int(11) NOT NULL,\n  `rating` tinyint(1) NOT NULL default '0',\n  UNIQUE KEY `reviewContact` (`reviewId`,`contactId`),\n  UNIQUE KEY `reviewContactRating` (`reviewId`,`contactId`,`rating`)\n) ENGINE=MyISAM DEFAULT CHARSET=utf8")) {
        $Conf->update_schema_version(12);
    }
    if ($Conf->sversion == 12 && Dbl::ql("alter table PaperReview add `reviewToken` int(11) NOT NULL default '0'")) {
        $Conf->update_schema_version(13);
    }
    if ($Conf->sversion == 13 && Dbl::ql("alter table OptionType add `optionValues` text NOT NULL default ''")) {
        $Conf->update_schema_version(14);
    }
    if ($Conf->sversion == 14 && Dbl::ql("insert into Settings (name, value) select 'rev_tokens', count(reviewId) from PaperReview where reviewToken!=0 on duplicate key update value=values(value)")) {
        $Conf->update_schema_version(15);
    }
    if ($Conf->sversion == 15) {
        // It's OK if this fails!  Update 11 added reviewRound to
        // PaperReviewArchive (so old databases have the column), but I forgot
        // to upgrade schema.sql (so new databases lack the column).
        Dbl::ql("alter table PaperReviewArchive add `reviewRound` tinyint(1) NOT NULL default '0'");
        $OK = true;
        $Conf->update_schema_version(16);
    }
    if ($Conf->sversion == 16 && Dbl::ql("alter table PaperReview add `reviewEditVersion` int(1) NOT NULL default '0'")) {
        $Conf->update_schema_version(17);
    }
    if ($Conf->sversion == 17 && Dbl::ql("alter table PaperReviewPreference add key `paperId` (`paperId`)") && Dbl::ql("create table PaperRank (\n  `paperId` int(11) NOT NULL,\n  `contactId` int(11) NOT NULL,\n  `rank` int(11) NOT NULL,\n  UNIQUE KEY `contactPaper` (`contactId`,`paperId`),\n  KEY `paperId` (`paperId`)\n) ENGINE=MyISAM DEFAULT CHARSET=utf8;")) {
        $Conf->update_schema_version(18);
    }
    if ($Conf->sversion == 18 && Dbl::ql("alter table PaperComment add `replyTo` int(11) NOT NULL")) {
        $Conf->update_schema_version(19);
    }
    if ($Conf->sversion == 19 && Dbl::ql("drop table PaperRank")) {
        $Conf->update_schema_version(20);
    }
    if ($Conf->sversion == 20 && Dbl::ql("alter table PaperComment add `timeNotified` int(11) NOT NULL default '0'")) {
        $Conf->update_schema_version(21);
    }
    if ($Conf->sversion == 21 && Dbl::ql("update PaperConflict set conflictType=8 where conflictType=3")) {
        $Conf->update_schema_version(22);
    }
    if ($Conf->sversion == 22 && Dbl::ql("insert into ChairAssistant (contactId) select contactId from Chair on duplicate key update ChairAssistant.contactId=ChairAssistant.contactId") && Dbl::ql("update ContactInfo set roles=roles+2 where roles=5")) {
        $Conf->update_schema_version(23);
    }
    if ($Conf->sversion == 23) {
        $Conf->update_schema_version(24);
    }
    if ($Conf->sversion == 24 && Dbl::ql("alter table ContactInfo add `preferredEmail` varchar(120)")) {
        $Conf->update_schema_version(25);
    }
    if ($Conf->sversion == 25) {
        if ($Conf->settings["final_done"] > 0 && !isset($Conf->settings["final_soft"]) && Dbl::ql("insert into Settings (name, value) values ('final_soft', " . $Conf->settings["final_done"] . ") on duplicate key update value=values(value)")) {
            $Conf->settings["final_soft"] = $Conf->settings["final_done"];
        }
        $Conf->update_schema_version(26);
    }
    if ($Conf->sversion == 26 && Dbl::ql("alter table PaperOption add `data` text") && Dbl::ql("alter table OptionType add `type` tinyint(1) NOT NULL default '0'") && Dbl::ql("update OptionType set type=2 where optionValues='i'") && Dbl::ql("update OptionType set type=1 where type=0 and optionValues!=''")) {
        $Conf->update_schema_version(27);
    }
    if ($Conf->sversion == 27 && Dbl::ql("alter table PaperStorage add `sha1` varbinary(20) NOT NULL default ''") && Dbl::ql("alter table PaperStorage add `documentType` int(3) NOT NULL default '0'") && Dbl::ql("update PaperStorage s, Paper p set s.sha1=p.sha1 where s.paperStorageId=p.paperStorageId and p.finalPaperStorageId=0 and s.paperStorageId>0") && Dbl::ql("update PaperStorage s, Paper p set s.sha1=p.sha1, s.documentType=" . DTYPE_FINAL . " where s.paperStorageId=p.finalPaperStorageId and s.paperStorageId>0")) {
        $Conf->update_schema_version(28);
    }
    if ($Conf->sversion == 28 && Dbl::ql("alter table OptionType add `sortOrder` tinyint(1) NOT NULL default '0'")) {
        $Conf->update_schema_version(29);
    }
    if ($Conf->sversion == 29 && Dbl::ql("delete from Settings where name='pldisplay_default'")) {
        $Conf->update_schema_version(30);
    }
    if ($Conf->sversion == 30 && Dbl::ql("DROP TABLE IF EXISTS `Formula`") && Dbl::ql("CREATE TABLE `Formula` (\n  `formulaId` int(11) NOT NULL auto_increment,\n  `name` varchar(200) NOT NULL,\n  `heading` varchar(200) NOT NULL default '',\n  `headingTitle` text NOT NULL default '',\n  `expression` text NOT NULL,\n  `authorView` tinyint(1) NOT NULL default '1',\n  PRIMARY KEY  (`formulaId`),\n  UNIQUE KEY `formulaId` (`formulaId`),\n  UNIQUE KEY `name` (`name`)\n) ENGINE=MyISAM DEFAULT CHARSET=utf8")) {
        $Conf->update_schema_version(31);
    }
    if ($Conf->sversion == 31 && Dbl::ql("alter table Formula add `createdBy` int(11) NOT NULL default '0'") && Dbl::ql("alter table Formula add `timeModified` int(11) NOT NULL default '0'") && Dbl::ql("alter table Formula drop index `name`")) {
        $Conf->update_schema_version(32);
    }
    if ($Conf->sversion == 32 && Dbl::ql("alter table PaperComment add key `timeModified` (`timeModified`)")) {
        $Conf->update_schema_version(33);
    }
    if ($Conf->sversion == 33 && Dbl::ql("alter table PaperComment add `paperStorageId` int(11) NOT NULL default '0'")) {
        $Conf->update_schema_version(34);
    }
    if ($Conf->sversion == 34 && Dbl::ql("alter table ContactInfo add `contactTags` text")) {
        $Conf->update_schema_version(35);
    }
    if ($Conf->sversion == 35 && Dbl::ql("alter table ContactInfo modify `defaultWatch` int(11) NOT NULL default '2'") && Dbl::ql("alter table PaperWatch modify `watch` int(11) NOT NULL default '0'")) {
        $Conf->update_schema_version(36);
    }
    if ($Conf->sversion == 36 && Dbl::ql("alter table PaperReview add `reviewNotified` int(1) default NULL") && Dbl::ql("alter table PaperReviewArchive add `reviewNotified` int(1) default NULL")) {
        $Conf->update_schema_version(37);
    }
    if ($Conf->sversion == 37 && Dbl::ql("alter table OptionType add `displayType` tinyint(1) NOT NULL default '0'")) {
        $Conf->update_schema_version(38);
    }
    if ($Conf->sversion == 38 && Dbl::ql("update PaperComment set forReviewers=1 where forReviewers=-1")) {
        $Conf->update_schema_version(39);
    }
    if ($Conf->sversion == 39 && Dbl::ql("CREATE TABLE `MailLog` (\n  `mailId` int(11) NOT NULL auto_increment,\n  `recipients` varchar(200) NOT NULL,\n  `paperIds` text,\n  `cc` text,\n  `replyto` text,\n  `subject` text,\n  `emailBody` text,\n  PRIMARY KEY  (`mailId`)\n) ENGINE=MyISAM DEFAULT CHARSET=utf8")) {
        $Conf->update_schema_version(40);
    }
    if ($Conf->sversion == 40 && Dbl::ql("alter table Paper add `capVersion` int(1) NOT NULL default '0'")) {
        $Conf->update_schema_version(41);
    }
    if ($Conf->sversion == 41 && Dbl::ql("alter table Paper modify `mimetype` varchar(80) NOT NULL default ''") && Dbl::ql("alter table PaperStorage modify `mimetype` varchar(80) NOT NULL default ''")) {
        $Conf->update_schema_version(42);
    }
    if ($Conf->sversion == 42 && Dbl::ql("alter table PaperComment add `ordinal` int(11) NOT NULL default '0'")) {
        $Conf->update_schema_version(43);
    }
    if ($Conf->sversion == 42 && ($result = Dbl::ql("describe PaperComment `ordinal`")) && ($o = edb_orow($result)) && substr($o->Type, 0, 3) == "int" && (!$o->Null || $o->Null == "NO") && (!$o->Default || $o->Default == "0")) {
        $Conf->update_schema_version(43);
    }
    if ($Conf->sversion == 43 && Dbl::ql("alter table Paper add `withdrawReason` text")) {
        $Conf->update_schema_version(44);
    }
    if ($Conf->sversion == 44 && Dbl::ql("alter table PaperStorage add `filename` varchar(255)")) {
        $Conf->update_schema_version(45);
    }
    if ($Conf->sversion == 45 && Dbl::ql("alter table PaperReview add `timeRequested` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperReview add `timeRequestNotified` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperReviewArchive add `timeRequested` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperReviewArchive add `timeRequestNotified` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperReview drop column `requestedOn`") && Dbl::ql("alter table PaperReviewArchive drop column `requestedOn`")) {
        $Conf->update_schema_version(46);
    }
    if ($Conf->sversion == 46 && Dbl::ql("alter table ContactInfo add `disabled` tinyint(1) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(47);
    }
    if ($Conf->sversion == 47 && Dbl::ql("delete from ti using TopicInterest ti left join TopicArea ta using (topicId) where ta.topicId is null")) {
        $Conf->update_schema_version(48);
    }
    if ($Conf->sversion == 48 && Dbl::ql("alter table PaperReview add `reviewAuthorNotified` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperReviewArchive add `reviewAuthorNotified` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperReviewArchive add `reviewToken` int(11) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(49);
    }
    if ($Conf->sversion == 49 && Dbl::ql("alter table PaperOption drop index `paperOption`") && Dbl::ql("alter table PaperOption add index `paperOption` (`paperId`,`optionId`,`value`)")) {
        $Conf->update_schema_version(50);
    }
    if ($Conf->sversion == 50 && Dbl::ql("alter table Paper add `managerContactId` int(11) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(51);
    }
    if ($Conf->sversion == 51 && Dbl::ql("alter table Paper drop column `numComments`") && Dbl::ql("alter table Paper drop column `numAuthorComments`")) {
        $Conf->update_schema_version(52);
    }
    // Due to a bug in the schema updater, some databases might have
    // sversion>=53 but no `PaperComment.commentType` column. Fix them.
    if (($Conf->sversion == 52 || $Conf->sversion >= 53 && ($result = Dbl::ql("show columns from PaperComment like 'commentType'")) && edb_nrows($result) == 0) && Dbl::ql("lock tables PaperComment write, Settings write") && Dbl::ql("alter table PaperComment add `commentType` int(11) NOT NULL DEFAULT '0'")) {
        $new_sversion = max($Conf->sversion, 53);
        $result = Dbl::ql("show columns from PaperComment like 'forAuthors'");
        if (!$result || edb_nrows($result) == 0 || Dbl::ql("update PaperComment set commentType=" . (COMMENTTYPE_AUTHOR | COMMENTTYPE_RESPONSE) . " where forAuthors=2") && Dbl::ql("update PaperComment set commentType=commentType|" . COMMENTTYPE_DRAFT . " where forAuthors=2 and forReviewers=0") && Dbl::ql("update PaperComment set commentType=" . COMMENTTYPE_ADMINONLY . " where forAuthors=0 and forReviewers=2") && Dbl::ql("update PaperComment set commentType=" . COMMENTTYPE_PCONLY . " where forAuthors=0 and forReviewers=0") && Dbl::ql("update PaperComment set commentType=" . COMMENTTYPE_REVIEWER . " where forAuthors=0 and forReviewers=1") && Dbl::ql("update PaperComment set commentType=" . COMMENTTYPE_AUTHOR . " where forAuthors!=0 and forAuthors!=2") && Dbl::ql("update PaperComment set commentType=commentType|" . COMMENTTYPE_BLIND . " where blind=1")) {
            $Conf->update_schema_version($new_sversion);
        }
    }
    if ($Conf->sversion < 53) {
        Dbl::qx_raw($Conf->dblink, "alter table PaperComment drop column `commentType`");
    }
    Dbl::ql("unlock tables");
    if ($Conf->sversion == 53 && Dbl::ql("alter table PaperComment drop column `forReviewers`") && Dbl::ql("alter table PaperComment drop column `forAuthors`") && Dbl::ql("alter table PaperComment drop column `blind`")) {
        $Conf->update_schema_version(54);
    }
    if ($Conf->sversion == 54 && Dbl::ql("alter table PaperStorage add `infoJson` varchar(255) DEFAULT NULL")) {
        $Conf->update_schema_version(55);
    }
    if ($Conf->sversion == 55 && Dbl::ql("alter table ContactInfo modify `password` varbinary(2048) NOT NULL")) {
        $Conf->update_schema_version(56);
    }
    if ($Conf->sversion == 56 && Dbl::ql("alter table Settings modify `data` blob")) {
        $Conf->update_schema_version(57);
    }
    if ($Conf->sversion == 57 && Dbl::ql("DROP TABLE IF EXISTS `Capability`") && Dbl::ql("CREATE TABLE `Capability` (\n  `capabilityId` int(11) NOT NULL AUTO_INCREMENT,\n  `capabilityType` int(11) NOT NULL,\n  `contactId` int(11) NOT NULL,\n  `paperId` int(11) NOT NULL,\n  `timeExpires` int(11) NOT NULL,\n  `salt` varbinary(255) NOT NULL,\n  `data` blob,\n  PRIMARY KEY (`capabilityId`),\n  UNIQUE KEY `capabilityId` (`capabilityId`)\n) ENGINE=MyISAM DEFAULT CHARSET=utf8") && Dbl::ql("DROP TABLE IF EXISTS `CapabilityMap`") && Dbl::ql("CREATE TABLE `CapabilityMap` (\n  `capabilityValue` varbinary(255) NOT NULL,\n  `capabilityId` int(11) NOT NULL,\n  `timeExpires` int(11) NOT NULL,\n  PRIMARY KEY (`capabilityValue`),\n  UNIQUE KEY `capabilityValue` (`capabilityValue`)\n) ENGINE=MyISAM DEFAULT CHARSET=utf8")) {
        $Conf->update_schema_version(58);
    }
    if ($Conf->sversion == 58 && Dbl::ql("alter table PaperReview modify `paperSummary` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReview modify `commentsToAuthor` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReview modify `commentsToPC` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReview modify `commentsToAddress` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReview modify `weaknessOfPaper` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReview modify `strengthOfPaper` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReview modify `textField7` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReview modify `textField8` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `paperSummary` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `commentsToAuthor` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `commentsToPC` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `commentsToAddress` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `weaknessOfPaper` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `strengthOfPaper` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `textField7` mediumtext DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive modify `textField8` mediumtext DEFAULT NULL")) {
        $Conf->update_schema_version(59);
    }
    if ($Conf->sversion == 59 && Dbl::ql("alter table ActionLog modify `action` varbinary(4096) NOT NULL") && Dbl::ql("alter table ContactInfo modify `note` varbinary(4096) DEFAULT NULL") && Dbl::ql("alter table ContactInfo modify `collaborators` varbinary(32767) DEFAULT NULL") && Dbl::ql("alter table ContactInfo modify `contactTags` varbinary(4096) DEFAULT NULL") && Dbl::ql("alter table Formula modify `headingTitle` varbinary(4096) NOT NULL") && Dbl::ql("alter table Formula modify `expression` varbinary(4096) NOT NULL") && Dbl::ql("alter table OptionType modify `description` varbinary(8192) DEFAULT NULL") && Dbl::ql("alter table OptionType modify `optionValues` varbinary(8192) NOT NULL") && Dbl::ql("alter table PaperReviewRefused modify `reason` varbinary(32767) DEFAULT NULL") && Dbl::ql("alter table ReviewFormField modify `description` varbinary(32767) DEFAULT NULL") && Dbl::ql("alter table ReviewFormOptions modify `description` varbinary(32767) DEFAULT NULL") && Dbl::ql("alter table ReviewRequest modify `reason` varbinary(32767) DEFAULT NULL") && Dbl::ql("alter table Settings modify `data` varbinary(32767) DEFAULT NULL") && Dbl::ql("alter table ContactAddress modify `addressLine1` varchar(2048) NOT NULL") && Dbl::ql("alter table ContactAddress modify `addressLine2` varchar(2048) NOT NULL") && Dbl::ql("alter table ContactAddress modify `city` varchar(2048) NOT NULL") && Dbl::ql("alter table ContactAddress modify `state` varchar(2048) NOT NULL") && Dbl::ql("alter table ContactAddress modify `zipCode` varchar(2048) NOT NULL") && Dbl::ql("alter table ContactAddress modify `country` varchar(2048) NOT NULL") && Dbl::ql("alter table PaperTopic modify `topicId` int(11) NOT NULL") && Dbl::ql("alter table PaperTopic modify `paperId` int(11) NOT NULL") && Dbl::ql("drop table if exists ChairTag")) {
        $Conf->update_schema_version(60);
    }
    if ($Conf->sversion == 60 && Dbl::ql("insert into Settings (name,value,data) select concat('msg.',substr(name,1,length(name)-3)), value, data from Settings where name='homemsg' or name='conflictdefmsg'") && $Conf->update_schema_version(61)) {
        foreach (array("conflictdef", "home") as $k) {
            if (isset($Conf->settingTexts["{$k}msg"])) {
                $Conf->settingTexts["msg.{$k}"] = $Conf->settingTexts["{$k}msg"];
            }
        }
        $Conf->settings["allowPaperOption"] = 61;
    }
    if ($Conf->sversion == 61 && Dbl::ql("alter table Capability modify `data` varbinary(4096) DEFAULT NULL")) {
        $Conf->update_schema_version(62);
    }
    if (!isset($Conf->settings["outcome_map"])) {
        $ojson = array();
        $result = Dbl::ql("select * from ReviewFormOptions where fieldName='outcome'");
        while ($row = edb_orow($result)) {
            $ojson[$row->level] = $row->description;
        }
        $Conf->save_setting("outcome_map", 1, $ojson);
    }
    if ($Conf->sversion == 62 && isset($Conf->settings["outcome_map"])) {
        $Conf->update_schema_version(63);
    }
    if (!isset($Conf->settings["review_form"]) && $Conf->sversion < 65) {
        update_schema_create_review_form($Conf);
    }
    if ($Conf->sversion == 63 && isset($Conf->settings["review_form"])) {
        $Conf->update_schema_version(64);
    }
    if ($Conf->sversion == 64 && Dbl::ql("drop table if exists `ReviewFormField`") && Dbl::ql("drop table if exists `ReviewFormOptions`")) {
        $Conf->update_schema_version(65);
    }
    if (!isset($Conf->settings["options"]) && $Conf->sversion < 67) {
        update_schema_create_options($Conf);
    }
    if ($Conf->sversion == 65 && isset($Conf->settings["options"])) {
        $Conf->update_schema_version(66);
    }
    if ($Conf->sversion == 66 && Dbl::ql("drop table if exists `OptionType`")) {
        $Conf->update_schema_version(67);
    }
    if ($Conf->sversion == 67 && Dbl::ql("alter table PaperComment modify `comment` varbinary(32767) DEFAULT NULL") && Dbl::ql("alter table PaperComment add `commentTags` varbinary(1024) DEFAULT NULL")) {
        $Conf->update_schema_version(68);
    }
    if ($Conf->sversion == 68 && Dbl::ql("alter table PaperReviewPreference add `expertise` int(4) DEFAULT NULL")) {
        $Conf->update_schema_version(69);
    }
    if ($Conf->sversion == 69 && Dbl::ql("alter table Paper drop column `pcPaper`")) {
        $Conf->update_schema_version(70);
    }
    if ($Conf->sversion == 70 && Dbl::ql("alter table ContactInfo modify `voicePhoneNumber` varbinary(256) DEFAULT NULL") && Dbl::ql("alter table ContactInfo modify `faxPhoneNumber` varbinary(256) DEFAULT NULL") && Dbl::ql("alter table ContactInfo modify `collaborators` varbinary(8192) DEFAULT NULL") && Dbl::ql("alter table ContactInfo drop column `note`") && Dbl::ql("alter table ContactInfo add `data` varbinary(32767) DEFAULT NULL")) {
        $Conf->update_schema_version(71);
    }
    if ($Conf->sversion == 71 && Dbl::ql("alter table Settings modify `name` varbinary(256) DEFAULT NULL") && Dbl::ql("update Settings set name=rtrim(name)")) {
        $Conf->update_schema_version(72);
    }
    if ($Conf->sversion == 72 && Dbl::ql("update TopicInterest set interest=-2 where interest=0") && Dbl::ql("update TopicInterest set interest=4 where interest=2") && Dbl::ql("delete from TopicInterest where interest=1")) {
        $Conf->update_schema_version(73);
    }
    if ($Conf->sversion == 73 && Dbl::ql("alter table PaperStorage add `size` bigint(11) DEFAULT NULL") && Dbl::ql("update PaperStorage set `size`=length(paper) where paper is not null")) {
        $Conf->update_schema_version(74);
    }
    if ($Conf->sversion == 74 && Dbl::ql("alter table ContactInfo drop column `visits`")) {
        $Conf->update_schema_version(75);
    }
    if ($Conf->sversion == 75) {
        foreach (array("capability_gc", "s3_scope", "s3_signing_key") as $k) {
            if (isset($Conf->settings[$k])) {
                $Conf->save_setting("__" . $k, $Conf->settings[$k], get($Conf->settingTexts, $k));
                $Conf->save_setting($k, null);
            }
        }
        $Conf->update_schema_version(76);
    }
    if ($Conf->sversion == 76 && Dbl::ql("update PaperReviewPreference set expertise=-expertise")) {
        $Conf->update_schema_version(77);
    }
    if ($Conf->sversion == 77 && Dbl::ql("alter table MailLog add `q` varchar(4096)")) {
        $Conf->update_schema_version(78);
    }
    if ($Conf->sversion == 78 && Dbl::ql("alter table MailLog add `t` varchar(200)")) {
        $Conf->update_schema_version(79);
    }
    if ($Conf->sversion == 79 && Dbl::ql("alter table ContactInfo add `passwordTime` int(11) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(80);
    }
    if ($Conf->sversion == 80 && Dbl::ql("alter table PaperReview modify `reviewRound` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperReviewArchive modify `reviewRound` int(11) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(81);
    }
    if ($Conf->sversion == 81 && Dbl::ql("alter table PaperStorage add `filterType` int(3) DEFAULT NULL") && Dbl::ql("alter table PaperStorage add `originalStorageId` int(11) DEFAULT NULL")) {
        $Conf->update_schema_version(82);
    }
    if ($Conf->sversion == 82 && Dbl::ql("update Settings set name='msg.resp_instrux' where name='msg.responseinstructions'")) {
        $Conf->update_schema_version(83);
    }
    if ($Conf->sversion == 83 && Dbl::ql("alter table PaperComment add `commentRound` int(11) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(84);
    }
    if ($Conf->sversion == 84 && Dbl::ql("insert ignore into Settings (name, value) select 'resp_active', value from Settings where name='resp_open'")) {
        $Conf->update_schema_version(85);
    }
    if ($Conf->sversion == 85 && Dbl::ql("DROP TABLE IF EXISTS `PCMember`") && Dbl::ql("DROP TABLE IF EXISTS `ChairAssistant`") && Dbl::ql("DROP TABLE IF EXISTS `Chair`")) {
        $Conf->update_schema_version(86);
    }
    if ($Conf->sversion == 86 && update_schema_transfer_address($Conf)) {
        $Conf->update_schema_version(87);
    }
    if ($Conf->sversion == 87 && Dbl::ql("DROP TABLE IF EXISTS `ContactAddress`")) {
        $Conf->update_schema_version(88);
    }
    if ($Conf->sversion == 88 && Dbl::ql("alter table ContactInfo drop key `name`") && Dbl::ql("alter table ContactInfo drop key `affiliation`") && Dbl::ql("alter table ContactInfo drop key `email_3`") && Dbl::ql("alter table ContactInfo drop key `firstName_2`") && Dbl::ql("alter table ContactInfo drop key `lastName`")) {
        $Conf->update_schema_version(89);
    }
    if ($Conf->sversion == 89 && update_schema_unaccented_name($Conf)) {
        $Conf->update_schema_version(90);
    }
    if ($Conf->sversion == 90 && Dbl::ql("alter table PaperReview add `reviewAuthorSeen` int(11) DEFAULT NULL")) {
        $Conf->update_schema_version(91);
    }
    if ($Conf->sversion == 91 && Dbl::ql("alter table PaperReviewArchive add `reviewAuthorSeen` int(11) DEFAULT NULL")) {
        $Conf->update_schema_version(92);
    }
    if ($Conf->sversion == 92 && Dbl::ql("alter table Paper drop key `titleAbstractText`") && Dbl::ql("alter table Paper drop key `allText`") && Dbl::ql("alter table Paper drop key `authorText`") && Dbl::ql("alter table Paper modify `authorInformation` varbinary(8192) DEFAULT NULL") && Dbl::ql("alter table Paper modify `abstract` varbinary(16384) DEFAULT NULL") && Dbl::ql("alter table Paper modify `collaborators` varbinary(8192) DEFAULT NULL") && Dbl::ql("alter table Paper modify `withdrawReason` varbinary(1024) DEFAULT NULL")) {
        $Conf->update_schema_version(93);
    }
    if ($Conf->sversion == 93 && Dbl::ql("alter table TopicArea modify `topicName` varchar(200) DEFAULT NULL")) {
        $Conf->update_schema_version(94);
    }
    if ($Conf->sversion == 94 && Dbl::ql("alter table PaperOption modify `data` varbinary(32768) DEFAULT NULL")) {
        foreach (PaperOption::nonfixed_option_list($Conf) as $xopt) {
            if ($xopt->type === "text") {
                Dbl::ql("delete from PaperOption where optionId={$xopt->id} and data=''");
            }
        }
        $Conf->update_schema_version(95);
    }
    if ($Conf->sversion == 95 && Dbl::ql("alter table Capability add unique key `salt` (`salt`)") && Dbl::ql("update Capability join CapabilityMap using (capabilityId) set Capability.salt=CapabilityMap.capabilityValue") && Dbl::ql("drop table if exists `CapabilityMap`")) {
        $Conf->update_schema_version(96);
    }
    if ($Conf->sversion == 96 && Dbl::ql("alter table ContactInfo add `passwordIsCdb` tinyint(1) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(97);
    }
    if ($Conf->sversion == 97 && Dbl::ql("alter table PaperReview add `reviewWordCount` int(11) DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive add `reviewWordCount` int(11)  DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive drop key `reviewId`") && Dbl::ql("alter table PaperReviewArchive drop key `contactPaper`") && Dbl::ql("alter table PaperReviewArchive drop key `reviewSubmitted`") && Dbl::ql("alter table PaperReviewArchive drop key `reviewNeedsSubmit`") && Dbl::ql("alter table PaperReviewArchive drop key `reviewType`") && Dbl::ql("alter table PaperReviewArchive drop key `requestedBy`")) {
        $Conf->update_schema_version(98);
    }
    if ($Conf->sversion == 98) {
        update_schema_review_word_counts($Conf);
        $Conf->update_schema_version(99);
    }
    if ($Conf->sversion == 99 && Dbl::ql("alter table ContactInfo ENGINE=InnoDB") && Dbl::ql("alter table Paper ENGINE=InnoDB") && Dbl::ql("alter table PaperComment ENGINE=InnoDB") && Dbl::ql("alter table PaperConflict ENGINE=InnoDB") && Dbl::ql("alter table PaperOption ENGINE=InnoDB") && Dbl::ql("alter table PaperReview ENGINE=InnoDB") && Dbl::ql("alter table PaperStorage ENGINE=InnoDB") && Dbl::ql("alter table PaperTag ENGINE=InnoDB") && Dbl::ql("alter table PaperTopic ENGINE=InnoDB") && Dbl::ql("alter table Settings ENGINE=InnoDB")) {
        $Conf->update_schema_version(100);
    }
    if ($Conf->sversion == 100 && Dbl::ql("alter table ActionLog ENGINE=InnoDB") && Dbl::ql("alter table Capability ENGINE=InnoDB") && Dbl::ql("alter table Formula ENGINE=InnoDB") && Dbl::ql("alter table MailLog ENGINE=InnoDB") && Dbl::ql("alter table PaperReviewArchive ENGINE=InnoDB") && Dbl::ql("alter table PaperReviewPreference ENGINE=InnoDB") && Dbl::ql("alter table PaperReviewRefused ENGINE=InnoDB") && Dbl::ql("alter table PaperWatch ENGINE=InnoDB") && Dbl::ql("alter table ReviewRating ENGINE=InnoDB") && Dbl::ql("alter table ReviewRequest ENGINE=InnoDB") && Dbl::ql("alter table TopicArea ENGINE=InnoDB") && Dbl::ql("alter table TopicInterest ENGINE=InnoDB")) {
        $Conf->update_schema_version(101);
    }
    if ($Conf->sversion == 101 && Dbl::ql("alter table ActionLog modify `ipaddr` varbinary(32) DEFAULT NULL") && Dbl::ql("alter table MailLog modify `recipients` varbinary(200) NOT NULL") && Dbl::ql("alter table MailLog modify `q` varbinary(4096) DEFAULT NULL") && Dbl::ql("alter table MailLog modify `t` varbinary(200) DEFAULT NULL") && Dbl::ql("alter table Paper modify `mimetype` varbinary(80) NOT NULL DEFAULT ''") && Dbl::ql("alter table PaperStorage modify `mimetype` varbinary(80) NOT NULL DEFAULT ''") && Dbl::ql("alter table PaperStorage modify `filename` varbinary(255) DEFAULT NULL") && Dbl::ql("alter table PaperStorage modify `infoJson` varbinary(8192) DEFAULT NULL")) {
        $Conf->update_schema_version(102);
    }
    if ($Conf->sversion == 102 && Dbl::ql("alter table PaperReview modify `paperSummary` mediumblob") && Dbl::ql("alter table PaperReview modify `commentsToAuthor` mediumblob") && Dbl::ql("alter table PaperReview modify `commentsToPC` mediumblob") && Dbl::ql("alter table PaperReview modify `commentsToAddress` mediumblob") && Dbl::ql("alter table PaperReview modify `weaknessOfPaper` mediumblob") && Dbl::ql("alter table PaperReview modify `strengthOfPaper` mediumblob") && Dbl::ql("alter table PaperReview modify `textField7` mediumblob") && Dbl::ql("alter table PaperReview modify `textField8` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `paperSummary` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `commentsToAuthor` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `commentsToPC` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `commentsToAddress` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `weaknessOfPaper` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `strengthOfPaper` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `textField7` mediumblob") && Dbl::ql("alter table PaperReviewArchive modify `textField8` mediumblob")) {
        $Conf->update_schema_version(103);
    }
    if ($Conf->sversion == 103 && Dbl::ql("alter table Paper modify `title` varbinary(256) DEFAULT NULL") && Dbl::ql("alter table Paper drop key `title`")) {
        $Conf->update_schema_version(104);
    }
    if ($Conf->sversion == 104 && Dbl::ql("alter table PaperReview add `reviewFormat` tinyint(1) DEFAULT NULL") && Dbl::ql("alter table PaperReviewArchive add `reviewFormat` tinyint(1) DEFAULT NULL")) {
        $Conf->update_schema_version(105);
    }
    if ($Conf->sversion == 105 && Dbl::ql("alter table PaperComment add `commentFormat` tinyint(1) DEFAULT NULL")) {
        $Conf->update_schema_version(106);
    }
    if ($Conf->sversion == 106 && Dbl::ql("alter table PaperComment add `authorOrdinal` int(11) NOT NULL default '0'") && Dbl::ql("update PaperComment set authorOrdinal=ordinal where commentType>=" . COMMENTTYPE_AUTHOR)) {
        $Conf->update_schema_version(107);
    }
    // repair missing comment ordinals; reset incorrect `ordinal`s for
    // author-visible comments
    if ($Conf->sversion == 107) {
        $result = Dbl::ql("select paperId, commentId from PaperComment where ordinal=0 and (commentType&" . (COMMENTTYPE_RESPONSE | COMMENTTYPE_DRAFT) . ")=0 and commentType>=" . COMMENTTYPE_PCONLY . " and commentType<" . COMMENTTYPE_AUTHOR . " order by commentId");
        while ($row = edb_row($result)) {
            Dbl::ql("update PaperComment,\n(select coalesce(count(commentId),0) commentCount from Paper\n    left join PaperComment on (PaperComment.paperId=Paper.paperId and (commentType&" . (COMMENTTYPE_RESPONSE | COMMENTTYPE_DRAFT) . ")=0 and commentType>=" . COMMENTTYPE_PCONLY . " and commentType<" . COMMENTTYPE_AUTHOR . " and commentId<{$row['1']})\n    where Paper.paperId={$row['0']} group by Paper.paperId) t\nset ordinal=(t.commentCount+1) where commentId={$row['1']}");
        }
        $result = Dbl::ql("select paperId, commentId from PaperComment where ordinal=0 and (commentType&" . (COMMENTTYPE_RESPONSE | COMMENTTYPE_DRAFT) . ")=0 and commentType>=" . COMMENTTYPE_AUTHOR . " order by commentId");
        while ($row = edb_row($result)) {
            Dbl::ql("update PaperComment,\n(select coalesce(count(commentId),0) commentCount from Paper\n    left join PaperComment on (PaperComment.paperId=Paper.paperId and (commentType&" . (COMMENTTYPE_RESPONSE | COMMENTTYPE_DRAFT) . ")=0 and commentType>=" . COMMENTTYPE_AUTHOR . " and commentId<{$row['1']})\n    where Paper.paperId={$row['0']} group by Paper.paperId) t\nset authorOrdinal=(t.commentCount+1) where commentId={$row['1']}");
        }
        $result = Dbl::ql("select paperId, commentId from PaperComment where ordinal=authorOrdinal and (commentType&" . (COMMENTTYPE_RESPONSE | COMMENTTYPE_DRAFT) . ")=0 and commentType>=" . COMMENTTYPE_AUTHOR . " order by commentId");
        while ($row = edb_row($result)) {
            Dbl::ql("update PaperComment,\n(select coalesce(max(ordinal),0) maxOrdinal from Paper\n    left join PaperComment on (PaperComment.paperId=Paper.paperId and (commentType&" . (COMMENTTYPE_RESPONSE | COMMENTTYPE_DRAFT) . ")=0 and commentType>=" . COMMENTTYPE_PCONLY . " and commentType<" . COMMENTTYPE_AUTHOR . " and commentId<{$row['1']})\n    where Paper.paperId={$row['0']} group by Paper.paperId) t\nset ordinal=(t.maxOrdinal+1) where commentId={$row['1']}");
        }
        $Conf->update_schema_version(108);
    }
    // contact tags format change
    if ($Conf->sversion == 108 && Dbl::ql("update ContactInfo set contactTags=substr(replace(contactTags, ' ', '#0 ') from 3)") && Dbl::ql("update ContactInfo set contactTags=replace(contactTags, '#0#0 ', '#0 ')")) {
        $Conf->update_schema_version(109);
    }
    if ($Conf->sversion == 109 && Dbl::ql("alter table PaperTag modify `tagIndex` float NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(110);
    }
    if ($Conf->sversion == 110 && Dbl::ql("alter table ContactInfo drop `faxPhoneNumber`") && Dbl::ql("alter table ContactInfo add `country` varbinary(256) default null") && update_schema_transfer_country($Conf)) {
        $Conf->update_schema_version(111);
    }
    if ($Conf->sversion == 111) {
        update_schema_review_word_counts($Conf);
        $Conf->update_schema_version(112);
    }
    if ($Conf->sversion == 112 && Dbl::ql("alter table ContactInfo add `passwordUseTime` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table ContactInfo add `updateTime` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("update ContactInfo set passwordUseTime=lastLogin where passwordUseTime=0")) {
        $Conf->update_schema_version(113);
    }
    if ($Conf->sversion == 113 && Dbl::ql("drop table if exists `PaperReviewArchive`")) {
        $Conf->update_schema_version(114);
    }
    if ($Conf->sversion == 114 && Dbl::ql("alter table PaperReview add `timeDisplayed` int(11) NOT NULL DEFAULT '0'") && Dbl::ql("alter table PaperComment add `timeDisplayed` int(11) NOT NULL DEFAULT '0'")) {
        $Conf->update_schema_version(115);
    }
    if ($Conf->sversion == 115 && Dbl::ql("alter table Formula drop column `authorView`")) {
        $Conf->update_schema_version(116);
    }
    if ($Conf->sversion == 116 && Dbl::ql("alter table PaperComment add `commentOverflow` longblob DEFAULT NULL")) {
        $Conf->update_schema_version(117);
    }
    if ($Conf->sversion == 117 && update_schema_drop_keys_if_exist("PaperTopic", ["paperTopic", "PRIMARY"]) && Dbl::ql("alter table PaperTopic add primary key (`paperId`,`topicId`)") && update_schema_drop_keys_if_exist("TopicInterest", ["contactTopic", "PRIMARY"]) && Dbl::ql("alter table TopicInterest add primary key (`contactId`,`topicId`)")) {
        $Conf->update_schema_version(118);
    }
    if ($Conf->sversion == 118 && update_schema_drop_keys_if_exist("PaperTag", ["paperTag", "PRIMARY"]) && Dbl::ql("alter table PaperTag add primary key (`paperId`,`tag`)") && update_schema_drop_keys_if_exist("PaperReviewPreference", ["paperId", "PRIMARY"]) && Dbl::ql("alter table PaperReviewPreference add primary key (`paperId`,`contactId`)") && update_schema_drop_keys_if_exist("PaperConflict", ["contactPaper", "contactPaperConflict", "PRIMARY"]) && Dbl::ql("alter table PaperConflict add primary key (`contactId`,`paperId`)") && Dbl::ql("alter table MailLog modify `paperIds` blob") && Dbl::ql("alter table MailLog modify `cc` blob") && Dbl::ql("alter table MailLog modify `replyto` blob") && Dbl::ql("alter table MailLog modify `subject` blob") && Dbl::ql("alter table MailLog modify `emailBody` blob")) {
        $Conf->update_schema_version(119);
    }
    if ($Conf->sversion == 119 && update_schema_drop_keys_if_exist("PaperWatch", ["contactPaper", "contactPaperWatch", "PRIMARY"]) && Dbl::ql("alter table PaperWatch add primary key (`paperId`,`contactId`)")) {
        $Conf->update_schema_version(120);
    }
    if ($Conf->sversion == 120 && Dbl::ql("alter table Paper add `paperFormat` tinyint(1) DEFAULT NULL")) {
        $Conf->update_schema_version(121);
    }
    if ($Conf->sversion == 121 && Dbl::ql_raw("update PaperReview r, Paper p set r.reviewNeedsSubmit=1 where p.paperId=r.paperId and p.timeSubmitted<=0 and r.reviewSubmitted is null") && Dbl::ql_raw("update PaperReview r, Paper p, PaperReview rq set r.reviewNeedsSubmit=0 where p.paperId=r.paperId and p.paperId=rq.paperId and p.timeSubmitted<=0 and r.reviewType=" . REVIEW_SECONDARY . " and r.contactId=rq.requestedBy and rq.reviewType<" . REVIEW_SECONDARY . " and rq.reviewSubmitted is not null") && Dbl::ql_raw("update PaperReview r, Paper p, PaperReview rq set r.reviewNeedsSubmit=-1 where p.paperId=r.paperId and p.paperId=rq.paperId and p.timeSubmitted<=0 and r.reviewType=" . REVIEW_SECONDARY . " and r.contactId=rq.requestedBy and rq.reviewType<" . REVIEW_SECONDARY . " and r.reviewNeedsSubmit=0")) {
        $Conf->update_schema_version(122);
    }
    if ($Conf->sversion == 122 && Dbl::ql("alter table ReviewRequest add `reviewRound` int(1) DEFAULT NULL")) {
        $Conf->update_schema_version(123);
    }
    if ($Conf->sversion == 123 && Dbl::ql("update ContactInfo set disabled=1 where password='' and email regexp '^anonymous[0-9]*\$'")) {
        $Conf->update_schema_version(124);
    }
    if ($Conf->sversion == 124 && Dbl::ql("update ContactInfo set password='' where password='******' or passwordIsCdb")) {
        $Conf->update_schema_version(125);
    }
    if ($Conf->sversion == 125 && Dbl::ql("alter table ContactInfo drop column `passwordIsCdb`")) {
        $Conf->update_schema_version(126);
    }
    if ($Conf->sversion == 126 && Dbl::ql("update ContactInfo set disabled=1, password='' where email regexp '^anonymous[0-9]*\$'")) {
        $Conf->update_schema_version(127);
    }
    if ($Conf->sversion == 127 && Dbl::ql("update PaperReview set reviewWordCount=null")) {
        $Conf->update_schema_version(128);
    }
    if ($Conf->sversion == 128 && update_schema_bad_comment_timeDisplayed($Conf)) {
        $Conf->update_schema_version(129);
    }
    if ($Conf->sversion == 129 && Dbl::ql("update PaperComment set timeDisplayed=1 where timeDisplayed=0 and timeNotified>0")) {
        $Conf->update_schema_version(130);
    }
    if ($Conf->sversion == 130 && Dbl::ql("DROP TABLE IF EXISTS `PaperTagAnno`") && Dbl::ql("CREATE TABLE `PaperTagAnno` (\n  `tag` varchar(40) NOT NULL,   # see TAG_MAXLEN in header.php\n  `annoId` int(11) NOT NULL,\n  `tagIndex` float NOT NULL DEFAULT '0',\n  `heading` varbinary(8192) DEFAULT NULL,\n  `annoFormat` tinyint(1) DEFAULT NULL,\n  `infoJson` varbinary(32768) DEFAULT NULL,\n  PRIMARY KEY (`tag`,`annoId`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8")) {
        $Conf->update_schema_version(131);
    }
    if ($Conf->sversion == 131 && Dbl::ql("alter table PaperStorage modify `infoJson` varbinary(32768) DEFAULT NULL")) {
        $Conf->update_schema_version(132);
    }
    Dbl::ql("delete from Settings where name='__schema_lock'");
}
 function paperQuery($contact, $options = array())
 {
     // Options:
     //   "paperId" => $pid  Only paperId $pid (if array, any of those)
     //   "reviewId" => $rid Only paper reviewed by $rid
     //   "commentId" => $c  Only paper where comment is $c
     //   "finalized"        Only submitted papers
     //   "unsub"            Only unsubmitted papers
     //   "accepted"         Only accepted papers
     //   "active"           Only nonwithdrawn papers
     //   "author"           Only papers authored by $contactId
     //   "myReviewRequests" Only reviews requested by $contactId
     //   "myReviews"        All reviews authored by $contactId
     //   "myOutstandingReviews" All unsubmitted reviews auth by $contactId
     //   "myReviewsOpt"     myReviews, + include papers not yet reviewed
     //   "allReviews"       All reviews (multiple rows per paper)
     //   "allReviewScores"  All review scores (multiple rows per paper)
     //   "allComments"      All comments (multiple rows per paper)
     //   "reviewerName"     Include reviewer names
     //   "commenterName"    Include commenter names
     //   "reviewer" => $cid Include reviewerConflictType/reviewerReviewType
     //   "tags"             Include paperTags
     //   "tagIndex" => $tag Include tagIndex of named tag
     //   "tagIndex" => tag array -- include tagIndex, tagIndex1, ...
     //   "topics"
     //   "options"
     //   "scores" => array(fields to score)
     //   "assignments"
     //   "order" => $sql    $sql is SQL 'order by' clause (or empty)
     global $Opt;
     $reviewerQuery = isset($options["myReviews"]) || isset($options["allReviews"]) || isset($options["myReviewRequests"]) || isset($options["myReviewsOpt"]) || isset($options["myOutstandingReviews"]);
     $allReviewerQuery = isset($options["allReviews"]) || isset($options["allReviewScores"]);
     $scoresQuery = !$reviewerQuery && isset($options["allReviewScores"]);
     if (is_object($contact)) {
         $contactId = $contact->contactId;
     } else {
         $contactId = (int) $contact;
         $contact = null;
     }
     if (isset($options["reviewer"]) && is_object($options["reviewer"])) {
         $reviewerContactId = $options["reviewer"]->contactId;
     } else {
         if (isset($options["reviewer"])) {
             $reviewerContactId = $options["reviewer"];
         } else {
             $reviewerContactId = $contactId;
         }
     }
     if (get($options, "author")) {
         $myPaperReview = null;
     } else {
         if ($allReviewerQuery) {
             $myPaperReview = "MyPaperReview";
         } else {
             $myPaperReview = "PaperReview";
         }
     }
     // paper selection
     $paperset = array();
     if (isset($options["paperId"])) {
         $paperset[] = self::_cvt_numeric_set($options["paperId"]);
     }
     if (isset($options["reviewId"])) {
         if (is_numeric($options["reviewId"])) {
             $result = Dbl::qe("select paperId from PaperReview where reviewId=" . $options["reviewId"]);
             $paperset[] = self::_cvt_numeric_set(edb_first_columns($result));
         } else {
             if (preg_match('/^(\\d+)([A-Z][A-Z]?)$/i', $options["reviewId"], $m)) {
                 $result = Dbl::qe("select paperId from PaperReview where paperId={$m['1']} and reviewOrdinal=" . parseReviewOrdinal($m[2]));
                 $paperset[] = self::_cvt_numeric_set(edb_first_columns($result));
             } else {
                 $paperset[] = array();
             }
         }
     }
     if (isset($options["commentId"])) {
         $result = Dbl::qe("select paperId from PaperComment where commentId" . sql_in_numeric_set(self::_cvt_numeric_set($options["commentId"])));
         $paperset[] = self::_cvt_numeric_set(edb_first_columns($result));
     }
     if (count($paperset) > 1) {
         $paperset = array(call_user_func_array("array_intersect", $paperset));
     }
     $papersel = "";
     if (count($paperset)) {
         $papersel = "paperId" . sql_in_numeric_set($paperset[0]) . " and ";
     }
     // prepare query: basic tables
     $where = array();
     $joins = array("Paper");
     $cols = array("Paper.*, PaperConflict.conflictType");
     $aujoinwhere = null;
     if (get($options, "author") && $contact && ($aujoinwhere = $contact->actAuthorSql("PaperConflict", true))) {
         $where[] = $aujoinwhere;
     }
     if (get($options, "author") && !$aujoinwhere) {
         $joins[] = "join PaperConflict on (PaperConflict.paperId=Paper.paperId and PaperConflict.contactId={$contactId} and PaperConflict.conflictType>=" . CONFLICT_AUTHOR . ")";
     } else {
         $joins[] = "left join PaperConflict on (PaperConflict.paperId=Paper.paperId and PaperConflict.contactId={$contactId})";
     }
     // my review
     $qr = "";
     if ($contact && ($tokens = $contact->review_tokens())) {
         $qr = " or PaperReview.reviewToken in (" . join(", ", $tokens) . ")";
     }
     if (get($options, "myReviewRequests")) {
         $joins[] = "join PaperReview on (PaperReview.paperId=Paper.paperId and PaperReview.requestedBy={$contactId} and PaperReview.reviewType=" . REVIEW_EXTERNAL . ")";
     } else {
         if (get($options, "myReviews")) {
             $joins[] = "join PaperReview on (PaperReview.paperId=Paper.paperId and (PaperReview.contactId={$contactId}{$qr}))";
         } else {
             if (get($options, "myOutstandingReviews")) {
                 $joins[] = "join PaperReview on (PaperReview.paperId=Paper.paperId and (PaperReview.contactId={$contactId}{$qr}) and PaperReview.reviewNeedsSubmit!=0)";
             } else {
                 if (get($options, "myReviewsOpt")) {
                     $joins[] = "left join PaperReview on (PaperReview.paperId=Paper.paperId and (PaperReview.contactId={$contactId}{$qr}))";
                 } else {
                     if (get($options, "allReviews") || get($options, "allReviewScores")) {
                         $x = get($options, "reviewLimitSql") ? " and (" . $options["reviewLimitSql"] . ")" : "";
                         $joins[] = "join PaperReview on (PaperReview.paperId=Paper.paperId{$x})";
                     } else {
                         if (!get($options, "author")) {
                             $joins[] = "left join PaperReview on (PaperReview.paperId=Paper.paperId and (PaperReview.contactId={$contactId}{$qr}))";
                         }
                     }
                 }
             }
         }
     }
     // started reviews
     if (get($options, "startedReviewCount")) {
         $joins[] = "left join (select paperId, count(*) count from PaperReview where {$papersel}(reviewSubmitted or reviewNeedsSubmit>0) group by paperId) R_started on (R_started.paperId=Paper.paperId)";
         $cols[] = "coalesce(R_started.count,0) startedReviewCount";
     }
     // submitted reviews
     $j = "select paperId, count(*) count";
     $before_ncols = count($cols);
     if (get($options, "startedReviewCount")) {
         $cols[] = "coalesce(R_submitted.count,0) reviewCount";
     }
     if (get($options, "scores")) {
         foreach ($options["scores"] as $fid) {
             $cols[] = "R_submitted.{$fid}Scores";
             if ($myPaperReview) {
                 $cols[] = "{$myPaperReview}.{$fid}";
             }
             $j .= ", group_concat({$fid} order by reviewId) {$fid}Scores";
         }
     }
     if (get($options, "reviewTypes") || get($options, "reviewIdentities")) {
         $cols[] = "R_submitted.reviewTypes";
         $j .= ", group_concat(reviewType order by reviewId) reviewTypes";
     }
     if (get($options, "reviewIdentities")) {
         $cols[] = "R_submitted.reviewRequestedBys";
         $j .= ", group_concat(requestedBy order by reviewId) reviewRequestedBys";
         if ($this->review_blindness() == self::BLIND_OPTIONAL) {
             $cols[] = "R_submitted.reviewBlinds";
             $j .= ", group_concat(reviewBlind order by reviewId) reviewBlinds";
         }
         if ($contact && $contact->review_tokens()) {
             $cols[] = "R_submitted.reviewTokens";
             $j .= ", group_concat(reviewToken order by reviewId) reviewTokens";
         }
     }
     if (get($options, "reviewRounds")) {
         $cols[] = "R_submitted.reviewRounds";
         $j .= ", group_concat(reviewRound order by reviewId) reviewRounds";
     }
     if (get($options, "reviewWordCounts") && $this->sversion >= 99) {
         $cols[] = "R_submitted.reviewWordCounts";
         $j .= ", group_concat(reviewWordCount order by reviewId) reviewWordCounts";
     }
     if (get($options, "reviewOrdinals")) {
         $cols[] = "R_submitted.reviewOrdinals";
         $j .= ", group_concat(reviewOrdinal order by reviewId) reviewOrdinals";
     }
     if (get($options, "reviewTypes") || get($options, "scores") || get($options, "reviewContactIds") || get($options, "reviewOrdinals") || get($options, "reviewIdentities")) {
         $cols[] = "R_submitted.reviewContactIds";
         $j .= ", group_concat(contactId order by reviewId) reviewContactIds";
     }
     if (count($cols) != $before_ncols) {
         $joins[] = "left join ({$j} from PaperReview where {$papersel}reviewSubmitted>0 group by paperId) R_submitted on (R_submitted.paperId=Paper.paperId)";
     }
     // assignments
     if (get($options, "assignments")) {
         $j = "select paperId, group_concat(contactId order by reviewId) allReviewContactIds, group_concat(reviewType order by reviewId) allReviewTypes, group_concat(reviewRound order by reviewId) allReviewRounds";
         $cols[] = "R_all.allReviewContactIds, R_all.allReviewTypes, R_all.allReviewRounds";
         $joins[] = "left join ({$j} from PaperReview where {$papersel}true group by paperId) R_all on (R_all.paperId=Paper.paperId)";
     }
     // fields
     if (get($options, "author")) {
         $cols[] = "null reviewType, null reviewId, null myReviewType";
     } else {
         // see also papercolumn.php
         array_push($cols, "PaperReview.reviewType, PaperReview.reviewId", "PaperReview.reviewModified, PaperReview.reviewSubmitted", "PaperReview.reviewNeedsSubmit, PaperReview.reviewOrdinal", "PaperReview.reviewBlind, PaperReview.reviewToken", "PaperReview.contactId as reviewContactId, PaperReview.requestedBy", "max({$myPaperReview}.reviewType) as myReviewType", "max({$myPaperReview}.reviewSubmitted) as myReviewSubmitted", "min({$myPaperReview}.reviewNeedsSubmit) as myReviewNeedsSubmit", "{$myPaperReview}.contactId as myReviewContactId", "PaperReview.reviewRound");
     }
     if ($reviewerQuery || $scoresQuery) {
         $cols[] = "PaperReview.reviewEditVersion as reviewEditVersion";
         $cols[] = ($this->sversion >= 105 ? "PaperReview.reviewFormat" : "null") . " as reviewFormat";
         foreach (ReviewForm::all_fields() as $f) {
             if ($reviewerQuery || $f->has_options) {
                 $cols[] = "PaperReview.{$f->id} as {$f->id}";
             }
         }
     }
     if ($myPaperReview == "MyPaperReview") {
         $joins[] = "left join PaperReview as MyPaperReview on (MyPaperReview.paperId=Paper.paperId and MyPaperReview.contactId={$contactId})";
     }
     if (get($options, "topics")) {
         $cols[] = "(select group_concat(topicId) from PaperTopic where PaperTopic.paperId=Paper.paperId) topicIds";
     }
     if (get($options, "options") && (isset($this->settingTexts["options"]) || isset($Opt["optionsInclude"])) && PaperOption::count_option_list()) {
         $cols[] = "(select group_concat(PaperOption.optionId, '#', value) from PaperOption where paperId=Paper.paperId) optionIds";
     } else {
         if (get($options, "options")) {
             $cols[] = "'' as optionIds";
         }
     }
     if (get($options, "tags")) {
         $cols[] = "(select group_concat(' ', tag, '#', tagIndex order by tag separator '') from PaperTag where PaperTag.paperId=Paper.paperId) paperTags";
     }
     if (get($options, "tagIndex") && !is_array($options["tagIndex"])) {
         $options["tagIndex"] = array($options["tagIndex"]);
     }
     if (get($options, "tagIndex")) {
         foreach ($options["tagIndex"] as $i => $tag) {
             $cols[] = "(select tagIndex from PaperTag where PaperTag.paperId=Paper.paperId and PaperTag.tag='" . sqlq($tag) . "') tagIndex" . ($i ?: "");
         }
     }
     if (get($options, "reviewerPreference")) {
         $joins[] = "left join PaperReviewPreference on (PaperReviewPreference.paperId=Paper.paperId and PaperReviewPreference.contactId={$reviewerContactId})";
         $cols[] = "coalesce(PaperReviewPreference.preference, 0) as reviewerPreference";
         $cols[] = "PaperReviewPreference.expertise as reviewerExpertise";
     }
     if (get($options, "allReviewerPreference") || get($options, "desirability")) {
         $subq = "select paperId";
         if (get($options, "allReviewerPreference")) {
             $subq .= ", " . $this->query_all_reviewer_preference() . " as allReviewerPreference";
             $cols[] = "APRP.allReviewerPreference";
         }
         if (get($options, "desirability")) {
             $subq .= ", sum(if(preference<=-100,0,greatest(least(preference,1),-1))) as desirability";
             $cols[] = "coalesce(APRP.desirability,0) as desirability";
         }
         $subq .= " from PaperReviewPreference where {$papersel}true group by paperId";
         $joins[] = "left join ({$subq}) as APRP on (APRP.paperId=Paper.paperId)";
     }
     if (get($options, "allConflictType")) {
         $joins[] = "left join (select paperId, group_concat(concat(contactId,' ',conflictType) separator ',') as allConflictType from PaperConflict where {$papersel}conflictType>0 group by paperId) as AllConflict on (AllConflict.paperId=Paper.paperId)";
         $cols[] = "AllConflict.allConflictType";
     }
     if (get($options, "reviewer")) {
         $joins[] = "left join PaperConflict RPC on (RPC.paperId=Paper.paperId and RPC.contactId={$reviewerContactId})";
         $joins[] = "left join PaperReview RPR on (RPR.paperId=Paper.paperId and RPR.contactId={$reviewerContactId})";
         $cols[] = "RPC.conflictType reviewerConflictType, RPR.reviewType reviewerReviewType";
     }
     if (get($options, "allComments")) {
         $joins[] = "join PaperComment on (PaperComment.paperId=Paper.paperId)";
         $joins[] = "left join PaperConflict as CommentConflict on (CommentConflict.paperId=PaperComment.paperId and CommentConflict.contactId=PaperComment.contactId)";
         array_push($cols, "PaperComment.commentId, PaperComment.contactId as commentContactId", "CommentConflict.conflictType as commentConflictType", "PaperComment.timeModified, PaperComment.comment", "PaperComment.replyTo, PaperComment.commentType");
     }
     if (get($options, "reviewerName")) {
         if ($options["reviewerName"] === "lead" || $options["reviewerName"] === "shepherd") {
             $joins[] = "left join ContactInfo as ReviewerContactInfo on (ReviewerContactInfo.contactId=Paper." . $options['reviewerName'] . "ContactId)";
         } else {
             if (get($options, "allComments")) {
                 $joins[] = "left join ContactInfo as ReviewerContactInfo on (ReviewerContactInfo.contactId=PaperComment.contactId)";
             } else {
                 if (get($options, "reviewerName")) {
                     $joins[] = "left join ContactInfo as ReviewerContactInfo on (ReviewerContactInfo.contactId=PaperReview.contactId)";
                 }
             }
         }
         array_push($cols, "ReviewerContactInfo.firstName as reviewFirstName", "ReviewerContactInfo.lastName as reviewLastName", "ReviewerContactInfo.email as reviewEmail", "ReviewerContactInfo.lastLogin as reviewLastLogin");
     }
     if (get($options, "foldall")) {
         $cols[] = "1 as folded";
     }
     // conditions
     if (count($paperset)) {
         $where[] = "Paper.paperId" . sql_in_numeric_set($paperset[0]);
     }
     if (get($options, "finalized")) {
         $where[] = "timeSubmitted>0";
     } else {
         if (get($options, "unsub")) {
             $where[] = "timeSubmitted<=0";
         }
     }
     if (get($options, "accepted")) {
         $where[] = "outcome>0";
     }
     if (get($options, "undecided")) {
         $where[] = "outcome=0";
     }
     if (get($options, "active") || get($options, "myReviews") || get($options, "myReviewRequests")) {
         $where[] = "timeWithdrawn<=0";
     }
     if (get($options, "myLead")) {
         $where[] = "leadContactId={$contactId}";
     }
     if (get($options, "unmanaged")) {
         $where[] = "managerContactId=0";
     }
     $pq = "select " . join(",\n    ", $cols) . "\nfrom " . join("\n    ", $joins);
     if (count($where)) {
         $pq .= "\nwhere " . join("\n    and ", $where);
     }
     // grouping and ordering
     if (get($options, "allComments")) {
         $pq .= "\ngroup by Paper.paperId, PaperComment.commentId";
     } else {
         if ($reviewerQuery || $scoresQuery) {
             $pq .= "\ngroup by Paper.paperId, PaperReview.reviewId";
         } else {
             $pq .= "\ngroup by Paper.paperId";
         }
     }
     if (get($options, "order") && $options["order"] != "order by Paper.paperId") {
         $pq .= "\n" . $options["order"];
     } else {
         $pq .= "\norder by Paper.paperId";
         if ($reviewerQuery || $scoresQuery) {
             $pq .= ", PaperReview.reviewOrdinal";
         }
         if (isset($options["allComments"])) {
             $pq .= ", PaperComment.commentId";
         }
     }
     //Conf::msg_debugt($pq);
     return $pq . "\n";
 }
 function search_completion($category = "")
 {
     global $Conf, $Me;
     $res = array();
     if ($this->amPC && (!$category || $category === "ss")) {
         foreach ($Conf->settingTexts as $k => $v) {
             if (substr($k, 0, 3) == "ss:" && ($v = json_decode($v))) {
                 $res[] = $k;
             }
         }
     }
     array_push($res, "has:submission", "has:abstract", "has:finalcopy");
     if ($this->amPC && $Conf->has_any_manager()) {
         $res[] = "has:admin";
     }
     if ($this->amPC && $Conf->has_any_lead_or_shepherd()) {
         $res[] = "has:lead";
     }
     if ($this->contact->can_view_some_decision()) {
         $res[] = "has:decision";
         if (!$category || $category === "dec") {
             $res[] = array("pri" => -1, "nosort" => true, "i" => array("dec:any", "dec:none", "dec:yes", "dec:no"));
             $dm = $Conf->decision_map();
             unset($dm[0]);
             $res = array_merge($res, self::simple_search_completion("dec:", $dm, Text::SEARCH_UNPRIVILEGE_EXACT));
         }
     }
     if ($this->amPC || $this->contact->can_view_some_decision()) {
         $res[] = "has:shepherd";
     }
     if ($this->contact->can_view_some_review()) {
         array_push($res, "has:re", "has:cre", "has:ire", "has:pre", "has:comment", "has:aucomment");
     }
     if ($this->contact->is_reviewer()) {
         array_push($res, "has:primary", "has:secondary", "has:external");
     }
     foreach ($Conf->resp_round_list() as $i => $rname) {
         if (!in_array("has:response", $res)) {
             $res[] = "has:response";
         }
         if ($i) {
             $res[] = "has:{$rname}response";
         }
     }
     if ($this->contact->can_view_some_draft_response()) {
         foreach ($Conf->resp_round_list() as $i => $rname) {
             if (!in_array("has:draftresponse", $res)) {
                 $res[] = "has:draftresponse";
             }
             if ($i) {
                 $res[] = "has:draft{$rname}response";
             }
         }
     }
     foreach (PaperOption::user_option_list($this->contact) as $o) {
         if ($this->contact->can_view_some_paper_option($o)) {
             array_push($res, "has:{$o->abbr}", "opt:{$o->abbr}");
         }
     }
     if ($this->contact->is_reviewer() && $Conf->has_rounds() && (!$category || $category === "round")) {
         $res[] = array("pri" => -1, "nosort" => true, "i" => array("round:any", "round:none"));
         $rlist = array();
         foreach ($Conf->round_list() as $rnum => $round) {
             if ($rnum && $round !== ";") {
                 $rlist[$rnum] = $round;
             }
         }
         $res = array_merge($res, self::simple_search_completion("round:", $rlist));
     }
     if ($Conf->has_topics() && (!$category || $category === "topic")) {
         foreach ($Conf->topic_map() as $tname) {
             $res[] = "topic:\"{$tname}\"";
         }
     }
     if (!$category || $category === "style") {
         $res[] = array("pri" => -1, "nosort" => true, "i" => array("style:any", "style:none", "color:any", "color:none"));
         foreach (explode("|", TagInfo::BASIC_COLORS) as $t) {
             array_push($res, "style:{$t}", "color:{$t}");
         }
     }
     if (!$category || $category === "show" || $category === "hide") {
         $cats = array();
         $pl = new PaperList(new PaperSearch($Me, ""));
         foreach (PaperColumn::$by_name as $c) {
             if (($cat = $c->completion_name()) && $c->prepare($pl, PaperColumn::PREP_COMPLETION)) {
                 $cats[$cat] = true;
             }
         }
         foreach (PaperColumn::$factories as $f) {
             foreach ($f[1]->completion_instances($this->contact) as $c) {
                 if (($cat = $c->completion_name()) && $c->prepare($pl, PaperColumn::PREP_COMPLETION)) {
                     $cats[$cat] = true;
                 }
             }
         }
         foreach (array_keys($cats) as $cat) {
             array_push($res, "show:{$cat}", "hide:{$cat}");
         }
         array_push($res, "show:compact", "show:statistics", "show:rownumbers");
     }
     return $res;
 }
Esempio n. 16
0
function searchQuickref()
{
    global $rowidx, $Conf, $Opt, $Me;
    // how to report author searches?
    if ($Conf->subBlindNever()) {
        $aunote = "";
    } else {
        if (!$Conf->subBlindAlways()) {
            $aunote = "<br /><span class='hint'>Search uses fields visible to the searcher. For example, PC member searches do not examine anonymous authors.</span>";
        } else {
            $aunote = "<br /><span class='hint'>Search uses fields visible to the searcher. For example, PC member searches do not examine authors.</span>";
        }
    }
    // does a reviewer tag exist?
    $retag = meaningful_pc_tag() ?: "";
    _searchQuickrefRow("Basics", "", "all papers in the search category");
    _searchQuickrefRow("", "story", "“story” in title, abstract, authors{$aunote}");
    _searchQuickrefRow("", "119", "paper #119");
    _searchQuickrefRow("", "1 2 5 12-24 kernel", "papers in the numbered set with “kernel” in title, abstract, authors");
    _searchQuickrefRow("", "\"802\"", "“802” in title, abstract, authors (not paper #802)");
    _searchQuickrefRow("", "very new", "“very” <em>and</em> “new” in title, abstract, authors");
    _searchQuickrefRow("", "very AND new", "the same");
    _searchQuickrefRow("", "\"very new\"", "the phrase “very new” in title, abstract, authors");
    _searchQuickrefRow("", "very OR new", "<em>either</em> “very” <em>or</em> “new” in title, abstract, authors");
    _searchQuickrefRow("", "(very AND new) OR newest", "use parentheses to group");
    _searchQuickrefRow("", "very -new", "“very” <em>but not</em> “new” in title, abstract, authors");
    _searchQuickrefRow("", "very NOT new", "the same");
    _searchQuickrefRow("", "ve*", "words that <em>start with</em> “ve” in title, abstract, authors");
    _searchQuickrefRow("", "*me*", "words that <em>contain</em> “me” in title, abstract, authors");
    _searchQuickrefRow("Title", "ti:flexible", "title contains “flexible”");
    _searchQuickrefRow("Abstract", "ab:\"very novel\"", "abstract contains “very novel”");
    _searchQuickrefRow("Authors", "au:poletto", "author list contains “poletto”");
    if ($Me->isPC) {
        _searchQuickrefRow("", "au:pc", "one or more authors are PC members (author email matches PC email)");
    }
    _searchQuickrefRow("Collaborators", "co:liskov", "collaborators contains “liskov”");
    _searchQuickrefRow("Topics", "topic:link", "selected topics match “link”");
    $oex = array();
    foreach (PaperOption::option_list() as $o) {
        $oex = array_merge($oex, $o->example_searches());
    }
    if (count($oex)) {
        $section = "Options";
        foreach ($oex as $extype => $oex) {
            if ($extype === "has") {
                $desc = "paper has “" . htmlspecialchars($oex[1]->name) . "” submission option";
                $oabbr = array();
                foreach (PaperOption::option_list() as $ox) {
                    if ($ox !== $oex[1]) {
                        $oabbr[] = "“has:" . htmlspecialchars($ox->abbr) . "”";
                    }
                }
                if (count($oabbr)) {
                    $desc .= '<div class="hint">Other option ' . pluralx(count($oabbr), "search") . ': ' . commajoin($oabbr) . '</div>';
                }
            } else {
                if ($extype === "yes") {
                    $desc = "same meaning; abbreviations also accepted";
                } else {
                    if ($extype === "numeric") {
                        $desc = "paper’s “" . htmlspecialchars($oex[1]->name) . "” option has value &gt; 100";
                    } else {
                        if ($extype === "selector") {
                            $desc = "paper’s “" . htmlspecialchars($oex[1]->name) . "” option has value “" . htmlspecialchars($oex[1]->selector[1]) . "”";
                        } else {
                            if ($extype === "attachment-count") {
                                $desc = "paper has more than 2 “" . htmlspecialchars($oex[1]->name) . "” attachments";
                            } else {
                                if ($extype === "attachment-filename") {
                                    $desc = "paper has an “" . htmlspecialchars($oex[1]->name) . "” attachment with a .gif extension";
                                } else {
                                    continue;
                                }
                            }
                        }
                    }
                }
            }
            _searchQuickrefRow($section, $oex[0], $desc);
            $section = "";
        }
    }
    _searchQuickrefRow("<a href='" . hoturl("help", "t=tags") . "'>Tags</a>", "#discuss", "tagged “discuss” (“tag:discuss” also works)");
    _searchQuickrefRow("", "-#discuss", "not tagged “discuss”");
    _searchQuickrefRow("", "order:discuss", "tagged “discuss”, sort by tag order (“rorder:” for reverse order)");
    _searchQuickrefRow("", "#disc*", "matches any tag that <em>starts with</em> “disc”");
    $cx = null;
    $cm = array();
    foreach (TagInfo::defined_tags() as $t) {
        foreach ($t->colors ?: array() as $c) {
            $cx = $cx ?: $c;
            if ($cx === $c) {
                $cm[] = "“{$t->tag}”";
            }
        }
    }
    if (count($cm)) {
        array_unshift($cm, "“{$cx}”");
        _searchQuickrefRow("", "style:{$cx}", "tagged to appear {$cx} (tagged " . commajoin($cm, "or") . ")");
    }
    _searchQuickrefRow("Reviews", "re:me", "you are a reviewer");
    _searchQuickrefRow("", "re:fdabek", "“fdabek” in reviewer name/email");
    if ($retag) {
        _searchQuickrefRow("", "re:#{$retag}", "has a reviewer tagged “#" . $retag . "”");
    }
    _searchQuickrefRow("", "re:4", "four reviewers (assigned and/or completed)");
    if ($retag) {
        _searchQuickrefRow("", "re:#{$retag}>1", "at least two reviewers (assigned and/or completed) tagged “#" . $retag . "”");
    }
    _searchQuickrefRow("", "re:complete<3", "less than three completed reviews<br /><span class=\"hint\">Use “cre:<3” for short.</span>");
    _searchQuickrefRow("", "re:incomplete>0", "at least one incomplete review");
    _searchQuickrefRow("", "re:inprogress", "at least one in-progress review (started, but not completed)");
    _searchQuickrefRow("", "re:primary>=2", "at least two primary reviewers");
    _searchQuickrefRow("", "re:secondary", "at least one secondary reviewer");
    _searchQuickrefRow("", "re:external", "at least one external reviewer");
    _searchQuickrefRow("", "re:primary:fdabek:complete", "“fdabek” has completed a primary review");
    if ($r = meaningful_round_name()) {
        _searchQuickrefRow("", "re:{$r}", "review in round “" . htmlspecialchars($r) . "”");
    }
    _searchQuickrefRow("", "re:auwords<100", "has a review with less than 100 words in author-visible fields");
    if ($Conf->setting("rev_ratings") != REV_RATINGS_NONE) {
        _searchQuickrefRow("", "rate:+", "review was rated positively (“rate:-” and “rate:+>2” also work; can combine with “re:”)");
    }
    _searchQuickrefRow("Comments", "has:cmt", "at least one visible reviewer comment (not including authors’ response)");
    _searchQuickrefRow("", "cmt:>=3", "at least <em>three</em> visible reviewer comments");
    _searchQuickrefRow("", "has:aucmt", "at least one reviewer comment visible to authors");
    _searchQuickrefRow("", "cmt:sylvia", "“sylvia” (in name/email) wrote at least one visible comment; can combine with counts, use reviewer tags");
    $rnames = $Conf->resp_round_list();
    if (count($rnames) > 1) {
        _searchQuickrefRow("", "has:response", "has an author’s response");
        _searchQuickrefRow("", "has:{$rnames[1]}response", "has {$rnames['1']} response");
    } else {
        _searchQuickrefRow("", "has:response", "has author’s response");
    }
    _searchQuickrefRow("", "anycmt:>1", "at least two visible comments, possibly <em>including</em> author’s response");
    _searchQuickrefRow("Leads", "lead:fdabek", "“fdabek” (in name/email) is discussion lead");
    _searchQuickrefRow("", "lead:none", "no assigned discussion lead");
    _searchQuickrefRow("", "lead:any", "some assigned discussion lead");
    _searchQuickrefRow("Shepherds", "shep:fdabek", "“fdabek” (in name/email) is shepherd (“none” and “any” also work)");
    _searchQuickrefRow("Conflicts", "conflict:me", "you have a conflict with the paper");
    _searchQuickrefRow("", "conflict:fdabek", "“fdabek” (in name/email) has a conflict with the paper<br /><span class='hint'>This search is only available to chairs and to PC members who can see the paper’s author list.</span>");
    _searchQuickrefRow("", "conflict:pc", "some PC member has a conflict with the paper");
    _searchQuickrefRow("", "conflict:pc>2", "at least three PC members have conflicts with the paper");
    _searchQuickrefRow("", "reconflict:\"1 2 3\"", "a reviewer of paper 1, 2, or 3 has a conflict with the paper");
    _searchQuickrefRow("Preferences", "pref:fdabek>0", "“fdabek” (in name/email) has review preference &gt;&nbsp;0<br /><span class='hint'>PC members can search their own preferences; chairs can search anyone’s preferences.</span>");
    _searchQuickrefRow("", "pref:X", "some PC member has a preference expertise of “X” (expert)");
    _searchQuickrefRow("Status", "status:sub", "paper is submitted for review", "t=all");
    _searchQuickrefRow("", "status:unsub", "paper is neither submitted nor withdrawn", "t=all");
    _searchQuickrefRow("", "status:withdrawn", "paper has been withdrawn", "t=all");
    _searchQuickrefRow("", "has:final", "final copy uploaded");
    foreach ($Conf->decision_map() as $dnum => $dname) {
        if ($dnum) {
            break;
        }
    }
    $qdname = strtolower($dname);
    if (strpos($qdname, " ") !== false) {
        $qdname = "\"{$qdname}\"";
    }
    _searchQuickrefRow("Decision", "dec:{$qdname}", "decision is “" . htmlspecialchars($dname) . "” (partial matches OK)");
    _searchQuickrefRow("", "dec:yes", "one of the accept decisions");
    _searchQuickrefRow("", "dec:no", "one of the reject decisions");
    _searchQuickrefRow("", "dec:any", "decision specified");
    _searchQuickrefRow("", "dec:none", "decision unspecified");
    // find names of review fields to demonstrate syntax
    $farr = array(array(), array());
    foreach (ReviewForm::all_fields() as $f) {
        $fx = $f->has_options ? 0 : 1;
        $farr[$fx][] = $f->analyze();
    }
    $t = "Review&nbsp;fields";
    if (count($farr[0])) {
        $r = $farr[0][0];
        _searchQuickrefRow($t, $r->abbreviation1() . ":{$r->typical_score}", "at least one completed review has {$r->name_html} score {$r->typical_score}");
        _searchQuickrefRow("", "{$r->abbreviation}:{$r->typical_score}", "other abbreviations accepted");
        if (count($farr[0]) > 1) {
            $r2 = $farr[0][1];
            _searchQuickrefRow("", strtolower($r2->abbreviation) . ":{$r2->typical_score}", "other fields accepted (here, {$r2->name_html})");
        }
        if (isset($r->typical_score_range)) {
            _searchQuickrefRow("", "{$r->abbreviation}:{$r->typical_score0}..{$r->typical_score}", "completed reviews’ {$r->name_html} scores are in the {$r->typical_score0}&ndash;{$r->typical_score} range<br /><small>(all scores between {$r->typical_score0} and {$r->typical_score})</small>");
            _searchQuickrefRow("", "{$r->abbreviation}:{$r->typical_score_range}", "completed reviews’ {$r->name_html} scores <em>fill</em> the {$r->typical_score0}&ndash;{$r->typical_score} range<br /><small>(all scores between {$r->typical_score0} and {$r->typical_score}, with at least one {$r->typical_score0} and at least one {$r->typical_score})</small>");
        }
        if (!$r->option_letter) {
            list($greater, $less, $hint) = array("greater", "less", "");
        } else {
            $hint = "<br /><small>(better scores are closer to A than Z)</small>";
            if (defval($Opt, "smartScoreCompare")) {
                list($greater, $less) = array("better", "worse");
            } else {
                list($greater, $less) = array("worse", "better");
            }
        }
        _searchQuickrefRow("", "{$r->abbreviation}:>{$r->typical_score}", "at least one completed review has {$r->name_html} score {$greater} than {$r->typical_score}" . $hint);
        _searchQuickrefRow("", "{$r->abbreviation}:2<={$r->typical_score}", "at least two completed reviews have {$r->name_html} score {$less} than or equal to {$r->typical_score}");
        _searchQuickrefRow("", "{$r->abbreviation}:pc>{$r->typical_score}", "at least one completed PC review has {$r->name_html} score {$greater} than {$r->typical_score}");
        _searchQuickrefRow("", "{$r->abbreviation}:pc:2>{$r->typical_score}", "at least two completed PC reviews have {$r->name_html} score {$greater} than {$r->typical_score}");
        _searchQuickrefRow("", "{$r->abbreviation}:sylvia={$r->typical_score}", "“sylvia” (reviewer name/email) gave {$r->name_html} score {$r->typical_score}");
        $t = "";
    }
    if (count($farr[1])) {
        $r = $farr[1][0];
        _searchQuickrefRow($t, $r->abbreviation1() . ":finger", "at least one completed review has “finger” in the {$r->name_html} field");
        _searchQuickrefRow($t, "{$r->abbreviation}:finger", "other abbreviations accepted");
        _searchQuickrefRow($t, "{$r->abbreviation}:any", "at least one completed review has text in the {$r->name_html} field");
    }
    if (count($farr[0])) {
        $r = $farr[0][0];
        _searchQuickrefRow("<a href=\"" . hoturl("help", "t=formulas") . "\">Formulas</a>", "formula:all({$r->abbreviation}={$r->typical_score})", "all reviews have {$r->name_html} score {$r->typical_score}<br />" . "<span class='hint'><a href=\"" . hoturl("help", "t=formulas") . "\">Formulas</a> can express complex numerical queries across review scores and preferences.</span>");
        _searchQuickrefRow("", "f:all({$r->abbreviation}={$r->typical_score})", "“f” is shorthand for “formula”");
        _searchQuickrefRow("", "formula:var({$r->abbreviation})>0.5", "variance in {$r->abbreviation} is above 0.5");
        _searchQuickrefRow("", "formula:any({$r->abbreviation}={$r->typical_score} && pref<0)", "at least one reviewer had {$r->name_html} score {$r->typical_score} and review preference &lt; 0");
    }
    _searchQuickrefRow("Display", "show:tags show:conflicts", "show tags and PC conflicts in the results");
    _searchQuickrefRow("", "hide:title", "hide title in the results");
    if (count($farr[0])) {
        $r = $farr[0][0];
        _searchQuickrefRow("", "show:max({$r->abbreviation})", "show a <a href=\"" . hoturl("help", "t=formulas") . "\">formula</a>");
        _searchQuickrefRow("", "show:statistics", "show summary statistics for formulas");
        _searchQuickrefRow("", "sort:{$r->abbreviation}", "sort by score");
        _searchQuickrefRow("", "sort:\"{$r->abbreviation} variance\"", "sort by score variance");
    }
    _searchQuickrefRow("", "sort:-status", "sort by reverse status");
    _searchQuickrefRow("", "edit:#discuss", "edit the values for tag “#discuss”");
    _searchQuickrefRow("", "search1 THEN search2", "like “search1 OR search2”, but papers matching “search1” are grouped together and appear earlier in the sorting order");
    _searchQuickrefRow("", "1-5 THEN 6-10 show:compact", "display searches in compact columns");
    _searchQuickrefRow("", "search1 HIGHLIGHT search2", "search for “search1”, but <span class=\"taghl highlightmark\">highlight</span> papers in that list that match “search2” (also try HIGHLIGHT:pink, HIGHLIGHT:green, HIGHLIGHT:blue)");
}