function _searchQueryWord($word, $report_error) { global $Conf; // check for paper number or "#TAG" if (preg_match('/\\A#?(\\d+)(?:-#?(\\d+))?\\z/', $word, $m)) { $m[2] = isset($m[2]) && $m[2] ? $m[2] : $m[1]; return new SearchTerm("pn", 0, array(range($m[1], $m[2]), array())); } else { if (substr($word, 0, 1) === "#") { $qe = $this->_searchQueryWord("tag:" . $word, false); if (!$qe->is_false()) { return $qe; } } } // Allow searches like "ovemer>2"; parse as "ovemer:>2". if (preg_match('/\\A([-_A-Za-z0-9]+)((?:[=!<>]=?|≠|≤|≥)[^:]+)\\z/', $word, $m)) { $qe = $this->_searchQueryWord($m[1] . ":" . $m[2], false); if (!$qe->is_false()) { return $qe; } } $keyword = null; if (($colon = strpos($word, ":")) > 0) { $x = substr($word, 0, $colon); if (strpos($x, '"') === false) { $keyword = get(self::$_keywords, $x) ?: $x; $word = substr($word, $colon + 1); if ($word === false) { $word = ""; } } } // Treat unquoted "*", "ANY", and "ALL" as special; return true. if ($word === "*" || $word === "ANY" || $word === "ALL" || $word === "") { return new SearchTerm("t"); } else { if ($word === "NONE") { return new SearchTerm("f"); } } $qword = $word; $quoted = $word[0] === '"'; $negated = false; if ($quoted) { $word = str_replace('*', '\\*', preg_replace('/(?:\\A"|"\\z)/', '', $word)); } if ($keyword === "notag") { $keyword = "tag"; $negated = true; } $qt = array(); if ($keyword ? $keyword === "ti" : isset($this->fields["ti"])) { $this->_searchField($word, "ti", $qt); } if ($keyword ? $keyword === "ab" : isset($this->fields["ab"])) { $this->_searchField($word, "ab", $qt); } if ($keyword ? $keyword === "au" : isset($this->fields["au"])) { $this->_searchAuthors($word, $qt, $keyword, $quoted); } if ($keyword ? $keyword === "co" : isset($this->fields["co"])) { $this->_searchField($word, "co", $qt); } if ($keyword ? $keyword === "re" : isset($this->fields["re"])) { $this->_search_reviewer($qword, "re", $qt); } else { if ($keyword && get(self::$_canonical_review_keywords, $keyword)) { $this->_search_reviewer($qword, $keyword, $qt); } } if (preg_match('/\\A(?:(?:draft-?)?\\w*resp(?:onse)|\\w*resp(?:onse)?(-?draft)?|cmt|aucmt|anycmt)\\z/', $keyword)) { $this->_search_comment($word, $keyword, $qt, $quoted); } if ($keyword === "pref" && $this->amPC) { $this->_search_revpref($word, $qt, $quoted, false); } if ($keyword === "prefexp" && $this->amPC) { $this->_search_revpref($word, $qt, $quoted, true); } foreach (array("lead", "shepherd", "manager") as $ctype) { if ($keyword === $ctype) { $x = $this->_one_pc_matcher($word, $quoted); $qt[] = new SearchTerm("pf", self::F_XVIEW, array("{$ctype}ContactId", $x)); if ($ctype === "manager" && $word === "me" && !$quoted && $this->privChair) { $qt[] = new SearchTerm("pf", self::F_XVIEW, array("{$ctype}ContactId", "=0")); } } } if (($keyword ? $keyword === "tag" : isset($this->fields["tag"])) || $keyword === "order" || $keyword === "rorder") { $this->_search_tags($word, $keyword, $qt); } if ($keyword === "color") { $this->_search_color($word, $qt); } if ($keyword === "topic") { $type = "topic"; $value = null; if ($word === "none" || $word === "any") { $value = $word; } else { $x = strtolower(simplify_whitespace($word)); $tids = array(); foreach ($Conf->topic_map() as $tid => $tname) { if (strstr(strtolower($tname), $x) !== false) { $tids[] = $tid; } } if (count($tids) == 0 && $word !== "none" && $word !== "any") { $this->warn("“" . htmlspecialchars($x) . "” does not match any defined paper topic."); $type = "f"; } else { $value = $tids; } } $qt[] = new SearchTerm($type, self::F_XVIEW, $value); } if ($keyword === "option") { $this->_search_options($word, $qt, true); } if ($keyword === "status" || $keyword === "is") { $this->_search_status($word, $qt, $quoted, true); } if ($keyword === "decision") { $this->_search_status($word, $qt, $quoted, false); } if ($keyword === "conflict" && $this->amPC) { $this->_search_conflict($word, $qt, $quoted, false); } if ($keyword === "pcconflict" && $this->amPC) { $this->_search_conflict($word, $qt, $quoted, true); } if ($keyword === "reconflict" && $this->privChair) { $this->_searchReviewerConflict($word, $qt, $quoted); } if ($keyword === "round" && $this->amPC) { $this->reviewAdjust = true; if ($word === "none") { $qt[] = new SearchTerm("revadj", 0, array("round" => array(0))); } else { if ($word === "any") { $qt[] = new SearchTerm("revadj", 0, array("round" => range(1, count($Conf->round_list()) - 1))); } else { $x = simplify_whitespace($word); $rounds = Text::simple_search($x, $Conf->round_list()); if (count($rounds) == 0) { $this->warn("“" . htmlspecialchars($x) . "” doesn’t match a review round."); $qt[] = new SearchTerm("f"); } else { $qt[] = new SearchTerm("revadj", 0, array("round" => array_keys($rounds))); } } } } if ($keyword === "rate") { $this->_searchReviewRatings($word, $qt); } if ($keyword === "has") { $this->_search_has($word, $qt, $quoted); } if ($keyword === "formula") { $this->_search_formula($word, $qt, $quoted); } if ($keyword === "ss" && $this->amPC) { if ($nextq = self::_expand_saved_search($word, $this->_ssRecursion)) { $this->_ssRecursion[$word] = true; $qe = $this->_searchQueryType($nextq); unset($this->_ssRecursion[$word]); } else { $qe = null; } if (!$qe && $nextq === false) { $this->warn("Saved search “" . htmlspecialchars($word) . "” is incorrectly defined in terms of itself."); } else { if (!$qe && !$Conf->setting_data("ss:{$word}")) { $this->warn("There is no “" . htmlspecialchars($word) . "” saved search."); } else { if (!$qe) { $this->warn("The “" . htmlspecialchars($word) . "” saved search is defined incorrectly."); } } } $qt[] = $qe ?: new SearchTerm("f"); } if ($keyword === "HEADING") { $heading = simplify_whitespace($word); $qt[] = SearchTerm::make_float(["heading" => $heading]); } if ($keyword === "show" || $keyword === "hide" || $keyword === "edit" || $keyword === "sort" || $keyword === "showsort" || $keyword === "editsort") { $editing = strpos($keyword, "edit") !== false; $sorting = strpos($keyword, "sort") !== false; $views = array(); $a = $keyword === "hide" ? false : ($editing ? "edit" : true); $word = simplify_whitespace($word); $ch1 = substr($word, 0, 1); if ($ch1 === "-" && !$sorting) { list($a, $word) = array(false, substr($word, 1)); } $wtype = $word; if ($sorting) { $sort = self::parse_sorter($wtype); $wtype = $sort->type; } if ($wtype !== "" && $keyword !== "sort") { $views[$wtype] = $a; } $f = array("view" => $views); if ($sorting) { $f["sort"] = array($word); } $qt[] = SearchTerm::make_float($f); } // Finally, look for a review field. if ($keyword && !isset(self::$_keywords[$keyword]) && count($qt) == 0) { if ($field = ReviewForm::field_search($keyword)) { $this->_search_review_field($word, $field, $qt, $quoted); } else { if (!$this->_search_options("{$keyword}:{$word}", $qt, false) && $report_error) { $this->warn("Unrecognized keyword “" . htmlspecialchars($keyword) . "”."); } } } $qe = SearchTerm::make_op("or", $qt); return $negated ? SearchTerm::make_not($qe) : $qe; }
private static function _make_column($name) { if (($f = ReviewForm::field_search($name)) && $f->has_options && $f->display_order !== false) { $c = parent::lookup_local($f->id); $c = $c ?: PaperColumn::register(new ScorePaperColumn($f->id)); return $c; } else { return null; } }
private function _parse_expr(&$t, $level, $in_qc) { global $Conf; if (($t = ltrim($t)) === "") { return null; } $lpos = -strlen($t); $e = null; if ($t[0] === "(") { $t = substr($t, 1); $e = $this->_parse_ternary($t, false); $t = ltrim($t); if (!$e || $t === "" || $t[0] !== ")") { return null; } $t = substr($t, 1); } else { if ($t[0] === "-" || $t[0] === "+" || $t[0] === "!") { $op = $t[0]; $t = substr($t, 1); if (!($e = $this->_parse_expr($t, self::$opprec["u{$op}"], $in_qc))) { return null; } $e = $op == "!" ? new NegateFexpr($e) : new Fexpr($op, $e); } else { if (preg_match('/\\Aopt(?:ion)?:\\s*(.*)\\z/s', $t, $m)) { $rest = self::_pop_argument($m[1]); $os = PaperSearch::analyze_option_search($rest[1]); foreach ($os->warn as $w) { $this->_error_html[] = $w; } if (!count($os->os) && !count($os->warn)) { $this->_error_html[] = "“" . htmlspecialchars($rest[1]) . "” doesn’t match a submission option."; } if (!count($os->os)) { return null; } foreach ($os->os as $o) { $ex = new OptionFexpr($o->option); if ($o->kind) { $this->_error_html[] = "“" . htmlspecialchars($rest[1]) . "” can’t be used in formulas."; } else { if ($o->value_word === "") { /* stick with raw option fexpr */ } else { if (is_array($o->value) && $o->compar === "!=") { $ex = new NegateFexpr(new InFexpr($ex, $o->value)); } else { if (is_array($o->value)) { $ex = new InFexpr($ex, $o->value); } else { $ex = new Fexpr(get(self::$_oprewrite, $o->compar, $o->compar), $ex, new ConstantFexpr($o->value, $o->option)); } } } } $e = $e ? new Fexpr("||", $e, $ex) : $ex; } if ($os->negate) { $e = new NegateFexpr($e); } $t = $rest[2]; } else { if (preg_match('/\\Anot([\\s(].*|)\\z/i', $t, $m)) { $t = $m[1]; if (!($e = $this->_parse_expr($t, self::$opprec["u!"], $in_qc))) { return null; } $e = new NegateFexpr($e); } else { if (preg_match('/\\A(\\d+\\.?\\d*|\\.\\d+)(.*)\\z/s', $t, $m)) { $e = new ConstantFexpr($m[1] + 0.0); $t = $m[2]; } else { if (preg_match('/\\A(false|true)\\b(.*)\\z/si', $t, $m)) { $e = new ConstantFexpr($m[1], Fexpr::FBOOL); $t = $m[2]; } else { if (preg_match('/\\A(?:pid|paperid)\\b(.*)\\z/si', $t, $m)) { $e = new PidFexpr(); $t = $m[1]; } else { if (preg_match('/\\A(?:dec|decision):\\s*' . self::ARGUMENT_REGEX . '(.*)\\z/si', $t, $m)) { $e = $this->field_search_fexpr(["outcome", PaperSearch::matching_decisions($m[1])]); $t = $m[2]; } else { if (preg_match('/\\A(?:dec|decision)\\b(.*)\\z/si', $t, $m)) { $e = new DecisionFexpr(); $t = $m[1]; } else { if (preg_match('/\\A(?:is|status):\\s*' . self::ARGUMENT_REGEX . '(.*)\\z/si', $t, $m)) { $e = $this->field_search_fexpr(PaperSearch::status_field_matcher($m[1])); $t = $m[2]; } else { if (preg_match('/\\A(?:tag(?:\\s*:\\s*|\\s+)|#)(' . TAG_REGEX . ')(.*)\\z/is', $t, $m) || preg_match('/\\Atag\\s*\\(\\s*(' . TAG_REGEX . ')\\s*\\)(.*)\\z/is', $t, $m)) { $e = new TagFexpr($m[1], false); $t = $m[2]; } else { if (preg_match('/\\Atag(?:v|-?val|-?value)(?:\\s*:\\s*|\\s+)(' . TAG_REGEX . ')(.*)\\z/is', $t, $m) || preg_match('/\\Atag(?:v|-?val|-?value)\\s*\\(\\s*(' . TAG_REGEX . ')\\s*\\)(.*)\\z/is', $t, $m)) { $e = new TagFexpr($m[1], true); $t = $m[2]; } else { if (preg_match('/\\A(r|re|rev|review|r(?:|e|ev|eview)type|(?:|r|re|rev|review)round|reviewer|r(?:|e|ev|eview)(?:|au)words)(?::|(?=#))\\s*' . self::ARGUMENT_REGEX . '(.*)\\z/is', $t, $m)) { $e = $this->_reviewer_decoration($this->_reviewer_base($m[1]), $m[2]); $t = $m[3]; } else { if (preg_match('/\\A((?:r|re|rev|review)(?:type|round|(?:|au)words)|(?:round|reviewer))\\b(.*)\\z/is', $t, $m)) { $e = $this->_reviewer_base($m[1]); $t = $m[2]; } else { if (preg_match('/\\A(my|all|any|avg|average|mean|median|quantile|count|min|max|atminof|atmaxof|argmin|argmax|std(?:d?ev(?:_pop|_samp|[_.][ps])?)?|sum|var(?:iance)?(?:_pop|_samp|[_.][ps])?|wavg)\\b(.*)\\z/is', $t, $m)) { $t = $m[2]; if (!($e = $this->_parse_function($m[1], $t, true))) { return null; } } else { if (preg_match('/\\A(greatest|least|round|floor|trunc|ceil|log|sqrt|pow|exp)\\b(.*)\\z/is', $t, $m)) { $t = $m[2]; if (!($e = $this->_parse_function($m[1], $t, false))) { return null; } } else { if (preg_match('/\\Anull\\b(.*)\\z/s', $t, $m)) { $e = ConstantFexpr::cnull(); $t = $m[1]; } else { if (preg_match('/\\A(?:is:?)?(rev?|pc(?:rev?)?|pri(?:mary)?|sec(?:ondary)?|ext(?:ernal)?)\\b(.*)\\z/is', $t, $m)) { $rt = ReviewSearchMatcher::parse_review_type($m[1]); $op = $rt == 0 || $rt == REVIEW_PC ? ">=" : "=="; $e = new Fexpr($op, new RevtypeFexpr(), new ConstantFexpr($rt, Fexpr::FREVTYPE)); $t = $m[2]; } else { if (preg_match('/\\Atopicscore\\b(.*)\\z/is', $t, $m)) { $e = new TopicScoreFexpr(); $t = $m[1]; } else { if (preg_match('/\\Aconf(?:lict)?\\b(.*)\\z/is', $t, $m)) { $e = new ConflictFexpr(false); $t = $m[1]; } else { if (preg_match('/\\Apcconf(?:lict)?\\b(.*)\\z/is', $t, $m)) { $e = new ConflictFexpr(true); $t = $m[1]; } else { if (preg_match('/\\A(?:rev)?pref\\b(.*)\\z/is', $t, $m)) { $e = new PrefFexpr(false); $t = $m[1]; } else { if (preg_match('/\\A(?:rev)?prefexp(?:ertise)?\\b(.*)\\z/is', $t, $m)) { $e = new PrefFexpr(true); $t = $m[1]; } else { if (preg_match('/\\A([A-Za-z0-9_]+|\\".*?\\")(.*)\\z/s', $t, $m) && $m[1] !== "\"\"" && !preg_match('/\\A\\s*\\(/', $m[2])) { $field = $m[1]; $t = $m[2]; if ($quoted = $field[0] === "\"") { $field = substr($field, 1, strlen($field) - 2); } if (($f = ReviewForm::field_search($field)) && $f->has_options) { $e = new ScoreFexpr($f); } else { if (!$quoted) { $e = new ConstantFexpr($field, false); } else { return null; } } } } } } } } } } } } } } } } } } } } } } } } } } if (!$e) { return null; } $e->set_landmark($lpos, -strlen($t)); while (1) { if (($t = ltrim($t)) === "") { return $e; } else { if (preg_match(self::BINARY_OPERATOR_REGEX, $t, $m)) { $op = $m[0]; $tn = substr($t, strlen($m[0])); } else { if (preg_match('/\\A(and|or)([\\s(].*|)\\z/i', $t, $m)) { $op = strlen($m[1]) == 3 ? "&&" : "||"; $tn = $m[2]; } else { if (!$in_qc && substr($t, 0, 1) === ":") { $op = ":"; $tn = substr($t, 1); } else { return $e; } } } } $opprec = self::$opprec[$op]; if ($opprec < $level) { return $e; } $t = $tn; $op = get(self::$_oprewrite, $op) ?: $op; if (!($e2 = $this->_parse_expr($t, get(self::$_oprassoc, $op) ? $opprec : $opprec + 1, $in_qc))) { return null; } $e = new Fexpr($op, $e, $e2); $e->set_landmark($lpos, -strlen($t)); } }