function apply($pid, $contact, &$req, AssignmentState $state)
 {
     if (!($tag = get($req, "tag"))) {
         return "Tag missing.";
     }
     // index argument
     $xindex = get($req, "index");
     if ($xindex === null) {
         $xindex = get($req, "value");
     }
     if ($xindex !== null && ($xindex = trim($xindex)) !== "") {
         $tag = preg_replace(',\\A(#?.+)(?:[=!<>]=?|#|≠|≤|≥)(?:|-?\\d+(?:\\.\\d*)?|-?\\.\\d+|any|all|none|clear)\\z,i', '$1', $tag);
         if (!preg_match(',\\A(?:[=!<>]=?|#|≠|≤|≥),i', $xindex)) {
             $xindex = "#" . $xindex;
         }
         $tag .= $xindex;
     }
     // tag parsing; see also PaperSearch::_check_tag
     if ($tag[0] === "#") {
         $tag = substr($tag, 1);
     }
     $m = array(null, "", "", "", "");
     $xtag = $tag;
     if (preg_match(',\\A(.*?)([=!<>]=?|#|≠|≤|≥)(.*?)\\z,', $xtag, $xm)) {
         list($xtag, $m[3], $m[4]) = array($xm[1], $xm[2], $xm[3]);
     }
     if (!preg_match(',\\A(|[^#]*~)([a-zA-Z!@*_:.]+[-a-zA-Z0-9!@*_:.\\/]*)\\z,i', $xtag, $xm)) {
         return "Invalid tag “" . htmlspecialchars($xtag) . "”.";
     } else {
         if ($m[3] && $m[4] === "") {
             return "Value missing.";
         } else {
             if ($m[3] && !preg_match(',\\A([-+]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)|any|all|none|clear)\\z,', $m[4])) {
                 return "Value must be a number.";
             } else {
                 list($m[1], $m[2]) = array($xm[1], $xm[2]);
             }
         }
     }
     if ($m[1] == "~" || strcasecmp($m[1], "me~") == 0) {
         $m[1] = ($contact && $contact->contactId ?: $state->contact->contactId) . "~";
     }
     // ignore attempts to change vote tags
     if (!$m[1] && TagInfo::is_votish($m[2])) {
         return false;
     }
     // add and remove use different paths
     $isadd = $this->isadd && $m[4] !== "none" && $m[4] !== "clear";
     if ($isadd && strpos($tag, "*") !== false) {
         return "Tag wildcards aren’t allowed when adding tags.";
     }
     if (!$isadd) {
         return $this->apply_remove($pid, $contact, $state, $m);
     }
     // resolve twiddle portion
     if ($m[1] && $m[1] != "~~" && !ctype_digit(substr($m[1], 0, strlen($m[1]) - 1))) {
         $c = substr($m[1], 0, strlen($m[1]) - 1);
         $twiddlecids = ContactSearch::make_pc($c, $state->contact->contactId)->ids;
         if (count($twiddlecids) == 0) {
             return "“" . htmlspecialchars($c) . "” doesn’t match a PC member.";
         } else {
             if (count($twiddlecids) > 1) {
                 return "“" . htmlspecialchars($c) . "” matches more than one PC member; be more specific to disambiguate.";
             }
         }
         $m[1] = $twiddlecids[0] . "~";
     }
     // resolve tag portion
     if (preg_match(',\\A(?:none|any|all)\\z,i', $m[2])) {
         return "Tag “{$tag}” is reserved.";
     }
     $tag = $m[1] . $m[2];
     // resolve index portion
     if ($m[3] && $m[3] != "#" && $m[3] != "=" && $m[3] != "==") {
         return "“" . htmlspecialchars($m[3]) . "” isn’t allowed when adding tags.";
     }
     if ($this->isadd === self::NEXT || $this->isadd === self::NEXTSEQ) {
         $index = $this->apply_next_index($pid, $tag, $state, $m);
     } else {
         $index = $m[3] ? cvtnum($m[4], 0) : null;
     }
     // if you can't view the tag, you can't set the tag
     // (information exposure)
     if (!$state->contact->can_view_tag($state->prow($pid), $tag, $state->override)) {
         return $this->cannot_view_error($state, $pid, $tag);
     }
     // save assignment
     $ltag = strtolower($tag);
     if ($index === null && ($x = $state->query(array("type" => "tag", "pid" => $pid, "ltag" => $ltag)))) {
         $index = $x[0]["_index"];
     }
     $vtag = TagInfo::votish_base($tag);
     if ($vtag && TagInfo::is_vote($vtag) && !$index) {
         $state->remove(array("type" => "tag", "pid" => $pid, "ltag" => $ltag));
     } else {
         $state->add(array("type" => "tag", "pid" => $pid, "ltag" => $ltag, "_tag" => $tag, "_index" => $index ?: 0));
     }
     if ($vtag) {
         $this->account_votes($pid, $vtag, $state);
     }
 }
 function can_view_any_peruser_tags($tag)
 {
     return $this->privChair || $this->isPC && TagInfo::is_votish($tag);
 }
 public function link($tag)
 {
     if (ctype_digit($tag[0])) {
         $x = strlen($this->_contactId);
         if (substr($tag, 0, $x) != $this->_contactId || $tag[$x] !== "~") {
             return false;
         }
         $tag = substr($tag, $x);
     }
     $base = TagInfo::base($tag);
     if (TagInfo::has_votish() && (TagInfo::is_votish($base) || $base[0] === "~" && TagInfo::is_vote(substr($base, 1)))) {
         $q = "#{$base} showsort:-#{$base}";
     } else {
         if ($base === $tag) {
             $q = "#{$base}";
         } else {
             $q = "order:#{$base}";
         }
     }
     return hoturl("search", ["q" => $q]);
 }