function _paperLink($row)
 {
     global $Conf;
     $pt = $this->_paper_link_page ? $this->_paper_link_page : "paper";
     $pl = "p=" . $row->paperId;
     $doreview = isset($row->reviewId) && isset($row->reviewFirstName);
     if ($doreview) {
         $rord = unparseReviewOrdinal($row);
         if ($pt == "paper" && $row->reviewSubmitted > 0) {
             $pl .= "#r" . $rord;
         } else {
             $pl .= "&r=" . $rord;
             if ($row->reviewSubmitted > 0) {
                 $pl .= "&m=r";
             }
         }
     } else {
         if ($pt === "review") {
             $pt = "paper";
         }
     }
     return hoturl($pt, $pl);
 }
function reviewLinks($prow, $rrows, $crows, $rrow, $mode, &$allreviewslink)
{
    global $Conf, $Me;
    $conflictType = $Me->view_conflict_type($prow);
    $allow_admin = $Me->allow_administer($prow);
    $any_comments = false;
    $admin = $Me->can_administer($prow);
    $xsep = ' <span class="barsep">·</span> ';
    $nvisible = 0;
    $myrr = null;
    if ($rrows) {
        foreach ($rrows as $rr) {
            if ($Me->can_view_review($prow, $rr, null)) {
                $nvisible++;
            }
            if ($rr->contactId == $Me->contactId || !$myrr && $Me->is_my_review($rr)) {
                $myrr = $rr;
            }
        }
    }
    // comments
    $pret = "";
    if ($crows && count($crows) > 0 && !$rrow && $mode !== "edit") {
        $cids = array();
        $cnames = array();
        $tagger = new Tagger($Me);
        foreach ($crows as $cr) {
            if ($Me->can_view_comment($prow, $cr, null)) {
                if ($Me->can_view_comment_identity($prow, $cr, null)) {
                    $n = Text::abbrevname_html($cr->user());
                } else {
                    $n = "anonymous";
                }
                if ($cr->commentType & COMMENTTYPE_RESPONSE) {
                    $rname = $Conf->resp_round_name($cr->commentRound);
                    $n = $n === "anonymous" ? "" : " ({$n})";
                    if ($cr->commentType & COMMENTTYPE_DRAFT && $rname != "1") {
                        $n = "<i>Draft {$rname} Response</i>{$n}";
                    } else {
                        if ($cr->commentType & COMMENTTYPE_DRAFT) {
                            $n = "<i>Draft Response</i>{$n}";
                        } else {
                            if ($rname != "1") {
                                $n = "<i>{$rname} Response</i>{$n}";
                            } else {
                                $n = "<i>Response</i>{$n}";
                            }
                        }
                    }
                }
                $cids[] = $cid = CommentInfo::unparse_html_id($cr);
                $tclass = "cmtlink";
                if ($cr->commentTags && ($tags = Tagger::strip_nonviewable($cr->commentTags, $Me)) && $Me->can_view_comment_tags($prow, $cr, null) && ($color = TagInfo::color_classes($tags))) {
                    if (TagInfo::classes_have_colors($color)) {
                        $tclass .= " tagcolorspan";
                    }
                    $tclass .= " {$color} taghl";
                }
                $cnames[] = '<a class="' . $tclass . '" href="#' . $cid . '">' . $n . '</a>';
            }
        }
        if (count($cids) > 0) {
            $pret = '<div class="revnotes"><a href="#' . $cids[0] . '"><strong>' . plural(count($cids), "Comment") . '</strong></a>: <span class="nb">' . join(',</span> <span class="nb">', $cnames) . "</span></div>";
            $any_comments = true;
        }
    }
    $t = "";
    // see all reviews
    $allreviewslink = false;
    if (($nvisible > 1 || $nvisible > 0 && !$myrr) && ($mode !== "p" || $rrow)) {
        $allreviewslink = true;
        $x = '<a href="' . hoturl("paper", "p={$prow->paperId}") . '" class="xx">' . Ht::img("view24.png", "[All reviews]", "dlimg") . "&nbsp;<u>All reviews</u></a>";
        $t .= ($t === "" ? "" : $xsep) . $x;
    }
    // edit paper
    if ($mode !== "edit" && $prow->conflictType >= CONFLICT_AUTHOR && !$Me->can_administer($prow)) {
        $x = '<a href="' . hoturl("paper", "p={$prow->paperId}&amp;m=edit") . '" class="xx">' . Ht::img("edit24.png", "[Edit paper]", "dlimg") . "&nbsp;<u><strong>Edit paper</strong></u></a>";
        $t .= ($t === "" ? "" : $xsep) . $x;
    }
    // edit review
    if ($mode === "re" || $mode === "assign" && $t !== "" || !$prow) {
        /* no link */
    } else {
        if ($myrr && $rrow != $myrr) {
            $myrlink = unparseReviewOrdinal($myrr);
            $a = '<a href="' . hoturl("review", "p={$prow->paperId}&r={$myrlink}") . '" class="xx">';
            if ($Me->can_review($prow, $myrr)) {
                $x = $a . Ht::img("review24.png", "[Edit review]", "dlimg") . "&nbsp;<u><b>Edit your review</b></u></a>";
            } else {
                $x = $a . Ht::img("review24.png", "[Your review]", "dlimg") . "&nbsp;<u><b>Your review</b></u></a>";
            }
            $t .= ($t === "" ? "" : $xsep) . $x;
        } else {
            if (!$myrr && !$rrow && $Me->can_review($prow, null)) {
                $x = '<a href="' . hoturl("review", "p={$prow->paperId}&amp;m=re") . '" class="xx">' . Ht::img("review24.png", "[Write review]", "dlimg") . "&nbsp;<u><b>Write review</b></u></a>";
                $t .= ($t === "" ? "" : $xsep) . $x;
            }
        }
    }
    // review assignments
    if ($mode !== "assign" && $mode !== "edit" && $Me->can_request_review($prow, true)) {
        $x = '<a href="' . hoturl("assign", "p={$prow->paperId}") . '" class="xx">' . Ht::img("assign24.png", "[Assign]", "dlimg") . "&nbsp;<u>" . ($admin ? "Assign reviews" : "External reviews") . "</u></a>";
        $t .= ($t === "" ? "" : $xsep) . $x;
    }
    // new comment
    $nocmt = preg_match('/\\A(?:assign|contact|edit|re)\\z/', $mode);
    if (!$allreviewslink && !$nocmt && $Me->can_comment($prow, null)) {
        $x = '<a href="#cnew" onclick="return papercomment.edit_new()" class="xx">' . Ht::img("comment24.png", "[Add comment]", "dlimg") . "&nbsp;<u>Add comment</u></a>";
        $t .= ($t === "" ? "" : $xsep) . $x;
        $any_comments = true;
    }
    // new response
    if (!$nocmt && ($prow->conflictType >= CONFLICT_AUTHOR || $allow_admin) && ($rrounds = $Conf->time_author_respond())) {
        foreach ($rrounds as $i => $rname) {
            $cid = ($i ? $rname : "") . "response";
            $what = "Add";
            if ($crows) {
                foreach ($crows as $cr) {
                    if ($cr->commentType & COMMENTTYPE_RESPONSE && $cr->commentRound == $i) {
                        $what = "Edit";
                        if ($cr->commentType & COMMENTTYPE_DRAFT) {
                            $what = "Edit draft";
                        }
                    }
                }
            }
            $x = '<a href="#' . $cid . '" onclick=\'return papercomment.edit_response(' . json_encode($rname) . ')\' class="xx">' . Ht::img("comment24.png", "[{$what} response]", "dlimg") . "&nbsp;" . ($conflictType >= CONFLICT_AUTHOR ? '<u style="font-weight:bold">' : '<u>') . $what . ($i ? " {$rname}" : "") . ' response</u></a>';
            $t .= ($t === "" ? "" : $xsep) . $x;
            $any_comments = true;
        }
    }
    // override conflict
    if ($allow_admin && !$admin) {
        $x = '<a href="' . selfHref(array("forceShow" => 1)) . '" class="xx">' . Ht::img("override24.png", "[Override]", "dlimg") . "&nbsp;<u>Override conflict</u></a> to show reviewers and allow editing";
        $t .= ($t === "" ? "" : $xsep) . $x;
    } else {
        if ($Me->privChair && !$allow_admin) {
            $x = "You can’t override your conflict because this paper has an administrator.";
            $t .= ($t === "" ? "" : $xsep) . $x;
        }
    }
    if ($any_comments) {
        CommentInfo::echo_script($prow);
    }
    if (($list = SessionList::active()) && ($pret || $t)) {
        return '<div class="has_hotcrp_list" data-hotcrp-list="' . $list->listno . '">' . $pret . $t . '</div>';
    } else {
        return $pret . $t;
    }
}
function unparseReviewOrdinal($ord)
{
    if ($ord === null) {
        return "x";
    } else {
        if (is_object($ord)) {
            if ($ord->reviewOrdinal) {
                return $ord->paperId . unparseReviewOrdinal($ord->reviewOrdinal);
            } else {
                return $ord->reviewId;
            }
        } else {
            if ($ord <= 26) {
                return chr($ord + 64);
            } else {
                return chr(intval(($ord - 1) / 26) + 64) . chr(($ord - 1) % 26 + 65);
            }
        }
    }
}
 function reviewFlowEntry($contact, $rrow, $trclass)
 {
     // See also CommentInfo::unparse_flow_entry
     global $Conf;
     $barsep = " <span class='barsep'>·</span> ";
     $a = "<a href='" . hoturl("paper", "p={$rrow->paperId}#r" . unparseReviewOrdinal($rrow)) . "'";
     $t = "<tr class='{$trclass}'><td class='pl_activityicon'>" . $a . ">" . Ht::img("review24.png", "[Review]", "dlimg") . "</a></td><td class='pl_activityid pnum'>" . $a . ">#{$rrow->paperId}</a></td><td class='pl_activitymain'><small>" . $a . " class=\"ptitle\">" . htmlspecialchars($rrow->shortTitle);
     if (strlen($rrow->shortTitle) != strlen($rrow->title)) {
         $t .= "...";
     }
     $t .= "</a>";
     if ($contact->can_view_review_time($rrow, $rrow)) {
         $time = $Conf->parseableTime($rrow->reviewModified, false);
     } else {
         $time = $Conf->unparse_time_obscure($Conf->obscure_time($rrow->reviewModified));
     }
     $t .= $barsep . $time;
     if ($contact->can_view_review_identity($rrow, $rrow, false)) {
         $t .= $barsep . "<span class='hint'>review by</span> " . Text::user_html($rrow->reviewFirstName, $rrow->reviewLastName, $rrow->reviewEmail);
     }
     $t .= "</small><br /><a class='q'" . substr($a, 3) . ">";
     $revViewScore = $contact->view_score_bound($rrow, $rrow);
     if ($rrow->reviewSubmitted) {
         $t .= "Review #" . unparseReviewOrdinal($rrow) . " submitted";
         $xbarsep = $barsep;
     } else {
         $xbarsep = "";
     }
     foreach ($this->forder as $field => $f) {
         if ($f->view_score > $revViewScore && $f->has_options && $rrow->{$field}) {
             $t .= $xbarsep . $f->name_html . "&nbsp;" . $f->unparse_value($rrow->{$field}, ReviewField::VALUE_SC);
             $xbarsep = $barsep;
         }
     }
     return $t . "</a></td></tr>";
 }
function downloadForm($editable)
{
    global $rf, $Conf, $Me, $prow, $paperTable, $Opt;
    $explicit = true;
    if ($paperTable->rrow) {
        $rrows = array($paperTable->rrow);
    } else {
        if ($editable) {
            $rrows = array();
        } else {
            $rrows = $paperTable->viewable_rrows;
            $explicit = false;
        }
    }
    $text = "";
    foreach ($rrows as $rr) {
        if ($rr->reviewSubmitted) {
            $text .= downloadView($prow, $rr, $editable);
        }
    }
    foreach ($rrows as $rr) {
        if (!$rr->reviewSubmitted && ($explicit || $rr->reviewModified)) {
            $text .= downloadView($prow, $rr, $editable);
        }
    }
    if (count($rrows) == 0 && $editable) {
        $text .= downloadView($prow, null, $editable);
    }
    if (!$explicit) {
        $paperTable->resolveComments();
        foreach ($paperTable->crows as $cr) {
            if ($Me->can_view_comment($prow, $cr, false)) {
                $text .= $cr->unparse_text($Me, true) . "\n";
            }
        }
    }
    if (!$text) {
        $whyNot = $Me->perm_view_review($prow, null, null);
        return Conf::msg_error(whyNotText($whyNot ?: array("fail" => 1), "review"));
    }
    if ($editable) {
        $text = ReviewForm::textFormHeader(count($rrows) > 1) . $text;
    }
    $filename = (count($rrows) > 1 ? "reviews" : "review") . "-" . $prow->paperId;
    if (count($rrows) == 1 && $rrows[0]->reviewSubmitted) {
        $filename .= unparseReviewOrdinal($rrows[0]->reviewOrdinal);
    }
    downloadText($text, $filename, !$editable);
}
xassert_match($x->a[0], ",^x.txt:2(?::|\$),");
xassert_match($x->a[1], ",^x.txt:2(?::|\$),");
xassert_match($x->b->c, ",^x.txt:4(?::|\$),");
xassert_match($x->b->__LANDMARK__, ",^x.txt:3(?::|\$),");
// obscure_time tests
$t = $Conf->parse_time("1 Sep 2010 00:00:01");
$t0 = $Conf->obscure_time($t);
xassert_eqq($Conf->unparse_time_obscure($t0), "1 Sep 2010");
xassert_eqq($Conf->printableTime($t0), "1 Sep 2010 12pm EDT");
$t = $Conf->parse_time("1 Sep 2010 23:59:59");
$t0 = $Conf->obscure_time($t);
xassert_eqq($Conf->unparse_time_obscure($t0), "1 Sep 2010");
xassert_eqq($Conf->printableTime($t0), "1 Sep 2010 12pm EDT");
// review ordinal tests
foreach ([1 => "A", 26 => "Z", 27 => "AA", 28 => "AB", 51 => "AY", 52 => "AZ", 53 => "BA", 54 => "BB", 702 => "ZZ"] as $n => $t) {
    xassert_eqq(unparseReviewOrdinal($n), $t);
    xassert_eqq(parseReviewOrdinal($t), $n);
}
// ReviewField::make_abbreviation tests
xassert_eqq(ReviewField::make_abbreviation("novelty", 0, 0), "Nov");
xassert_eqq(ReviewField::make_abbreviation("novelty is an amazing", 0, 0), "NovIsAma");
xassert_eqq(ReviewField::make_abbreviation("novelty is an AWESOME", 0, 0), "NovIsAWESOME");
xassert_eqq(ReviewField::make_abbreviation("novelty isn't an AWESOME", 0, 0), "NovIsnAWESOME");
xassert_eqq(ReviewField::make_abbreviation("novelty isn't an AWESOME", 0, 1), "novelty-isnt-awesome");
xassert_eqq(ReviewField::make_abbreviation("_format", 0, 1), "format");
// utf8_word_prefix, etc. tests
xassert_eqq(UnicodeHelper::utf8_prefix("aaaaaaaa", 7), "aaaaaaa");
xassert_eqq(UnicodeHelper::utf8_prefix("aaaaaaaa", 8), "aaaaaaaa");
xassert_eqq(UnicodeHelper::utf8_prefix("aaaaaaaa", 9), "aaaaaaaa");
xassert_eqq(UnicodeHelper::utf8_prefix("áááááááá", 7), "ááááááá");
xassert_eqq(UnicodeHelper::utf8_prefix("áááááááá", 8), "áááááááá");
 private function _combine_data($result)
 {
     global $Me;
     $data = [];
     if ($this->fx->result_format() === Fexpr::FREVIEWER) {
         $this->_prepare_reviewer_color($Me);
     }
     $fxf = $this->fx->compile_function($Me);
     list($fytrack, $fycombine) = $this->fy->compile_combine_functions($Me);
     $reviewf = null;
     if ($this->fx->needs_review() || $this->fy->datatypes) {
         $reviewf = Formula::compile_indexes_function($Me, ($this->fx->needs_review() ? $this->fx->datatypes : 0) | $this->fy->datatypes);
     }
     while ($prow = PaperInfo::fetch($result, $Me)) {
         if (!$Me->can_view_paper($prow)) {
             continue;
         }
         $queries = $this->papermap[$prow->paperId];
         $s = $ps = $this->_paper_style($prow);
         $revs = $reviewf ? $reviewf($prow, $Me) : [null];
         foreach ($revs as $rcid) {
             if (($x = $fxf($prow, $rcid, $Me)) === null) {
                 continue;
             }
             if ($ps === self::REVIEWER_COLOR) {
                 $s = get($this->reviewer_color, $d[0]) ?: "";
             }
             $d = [$x, $fytrack($prow, $rcid, $Me), $prow->paperId, $s];
             if ($rcid && ($o = $prow->review_ordinal($rcid))) {
                 $d[2] .= unparseReviewOrdinal($o);
             }
             foreach ($queries as $q) {
                 $q && ($d[4] = $q);
                 $data[] = $d;
             }
         }
     }
     $is_sum = $this->fy->is_sum();
     usort($data, "FormulaGraph::barchart_compare");
     $ndata = [];
     for ($i = 0; $i != count($data); $i = $j) {
         $d = [$data[$i][0], [$data[$i][1]], [$data[$i][2]], $data[$i][3], get($data[$i], 4)];
         for ($j = $i + 1; $j != count($data) && $data[$j][0] == $d[0] && get($data[$j], 4) == $d[4] && (!$is_sum || $data[$j][3] == $d[3]); ++$j) {
             $d[1][] = $data[$j][1];
             $d[2][] = $data[$j][2];
             if ($d[3] && $d[3] != $data[$j][3]) {
                 $d[3] = "";
             }
         }
         $d[1] = $fycombine($d[1]);
         if (!$d[4]) {
             array_pop($d);
             $d[3] || array_pop($d);
         }
         $ndata[] = $d;
     }
     return $ndata;
 }
 function content($fieldId, $row)
 {
     global $Conf;
     switch ($fieldId) {
         case self::FIELD_NAME:
             $t = Text::name_html($row);
             if (trim($t) == "") {
                 $t = "[No name]";
             }
             $t = '<span class="taghl">' . $t . '</span>';
             if ($this->contact->privChair) {
                 $t = "<a href=\"" . hoturl("profile", "u=" . urlencode($row->email) . $this->contactLinkArgs) . "\"" . ($row->disabled ? " class='uu'" : "") . ">{$t}</a>";
             }
             if ($row->roles & Contact::ROLE_CHAIR) {
                 $t .= ' <span class="pcrole">(chair)</span>';
             } else {
                 if (($row->roles & (Contact::ROLE_ADMIN | Contact::ROLE_PC)) == (Contact::ROLE_ADMIN | Contact::ROLE_PC)) {
                     $t .= ' <span class="pcrole">(PC, sysadmin)</span>';
                 } else {
                     if ($row->roles & Contact::ROLE_ADMIN) {
                         $t .= ' <span class="pcrole">(sysadmin)</span>';
                     } else {
                         if ($row->roles & Contact::ROLE_PC && $this->limit != "pc") {
                             $t .= ' <span class="pcrole">(PC)</span>';
                         }
                     }
                 }
             }
             if ($this->contact->privChair && $row->email != $this->contact->email) {
                 $t .= " <a href=\"" . hoturl("index", "actas=" . urlencode($row->email)) . "\">" . Ht::img("viewas.png", "[Act as]", array("title" => "Act as " . Text::name_text($row))) . "</a>";
             }
             if ($row->disabled && $this->contact->isPC) {
                 $t .= ' <span class="hint">(disabled)</span>';
             }
             return $t;
         case self::FIELD_EMAIL:
             if (!$this->contact->isPC) {
                 return "";
             }
             $e = htmlspecialchars($row->email);
             if (strpos($row->email, "@") === false) {
                 return $e;
             } else {
                 return "<a href=\"mailto:{$e}\">{$e}</a>";
             }
         case self::FIELD_AFFILIATION:
         case self::FIELD_AFFILIATION_ROW:
             return htmlspecialchars($row->affiliation);
         case self::FIELD_LASTVISIT:
             if (!$row->lastLogin) {
                 return "Never";
             }
             return $Conf->printableTimeShort($row->lastLogin);
         case self::FIELD_SELECTOR:
         case self::FIELD_SELECTOR_ON:
             $this->any->sel = true;
             $c = "";
             if ($fieldId == self::FIELD_SELECTOR_ON) {
                 $c = " checked='checked'";
             }
             return "<input type='checkbox' class='cb' name='pap[]' value='{$row->contactId}' tabindex='1' id='psel{$this->count}' onclick='rangeclick(event,this)' {$c}/>";
         case self::FIELD_HIGHTOPICS:
         case self::FIELD_LOWTOPICS:
             if (!defval($row, "topicIds")) {
                 return "";
             }
             $wanthigh = $fieldId == self::FIELD_HIGHTOPICS;
             $topics = array_combine(explode(",", $row->topicIds), explode(",", $row->topicInterest));
             $nt = $nti = array();
             foreach ($topics as $k => $v) {
                 if ($wanthigh ? $v > 0 : $v < 0) {
                     $nt[] = $k;
                     $nti[] = $v;
                 }
             }
             if (count($nt)) {
                 return PaperInfo::unparse_topic_list_html($nt, $nti, true);
             } else {
                 return "";
             }
         case self::FIELD_REVIEWS:
             if (!$row->numReviews && !$row->numReviewsSubmitted) {
                 return "";
             }
             $a1 = "<a href=\"" . hoturl("search", "t=s&amp;q=re:" . urlencode($row->email)) . "\">";
             if ($row->numReviews == $row->numReviewsSubmitted) {
                 return "{$a1}<b>{$row->numReviewsSubmitted}</b></a>";
             } else {
                 return "{$a1}<b>{$row->numReviewsSubmitted}</b>/{$row->numReviews}</a>";
             }
         case self::FIELD_LEADS:
             if (!$row->numLeads) {
                 return "";
             }
             return "<a href=\"" . hoturl("search", "t=s&amp;q=lead:" . urlencode($row->email)) . "\">{$row->numLeads}</a>";
         case self::FIELD_SHEPHERDS:
             if (!$row->numShepherds) {
                 return "";
             }
             return "<a href=\"" . hoturl("search", "t=s&amp;q=shepherd:" . urlencode($row->email)) . "\">{$row->numShepherds}</a>";
         case self::FIELD_REVIEW_RATINGS:
             if (!$row->numReviews && !$row->numReviewsSubmitted || !$row->numRatings) {
                 return "";
             }
             $a = array();
             $b = array();
             if ($row->sumRatings > 0) {
                 $a[] = $row->sumRatings . " positive";
                 $b[] = "<a href=\"" . hoturl("search", "q=re:" . urlencode($row->email) . "+rate:%2B") . "\">+" . $row->sumRatings . "</a>";
             }
             if ($row->sumRatings < $row->numRatings) {
                 $a[] = $row->numRatings - $row->sumRatings . " negative";
                 $b[] = "<a href=\"" . hoturl("search", "q=re:" . urlencode($row->email) . "+rate:-") . "\">&minus;" . ($row->numRatings - $row->sumRatings) . "</a>";
             }
             return "<span class='hastitle' title='" . join(", ", $a) . "'>" . join(" ", $b) . "</span>";
         case self::FIELD_PAPERS:
             if (!$row->paperIds) {
                 return "";
             }
             $x = explode(",", $row->paperIds);
             sort($x, SORT_NUMERIC);
             foreach ($x as &$v) {
                 $v = '<a href="' . hoturl("paper", "p={$v}") . '">' . $v . '</a>';
             }
             $ls = "p/s/";
             if ($this->limit == "auuns" || $this->limit == "all") {
                 $ls = "p/all/";
             }
             $ls = htmlspecialchars($ls . urlencode("au:" . $row->email));
             return '<div class="has_hotcrp_list" data-hotcrp-list="' . $ls . '">' . join(", ", $x) . '</div>';
         case self::FIELD_REVIEW_PAPERS:
             if (!$row->paperIds) {
                 return "";
             }
             $pids = explode(",", $row->paperIds);
             $rids = explode(",", $row->reviewIds);
             $ords = explode(",", $row->reviewOrdinals);
             $spids = $pids;
             sort($spids, SORT_NUMERIC);
             $m = array();
             for ($i = 0; $i != count($pids); ++$i) {
                 if ($ords[$i]) {
                     $url = hoturl("paper", "p=" . $pids[$i] . "#r" . $pids[$i] . unparseReviewOrdinal($ords[$i]));
                 } else {
                     $url = hoturl("review", "p=" . $pids[$i] . "&amp;r=" . $rids[$i]);
                 }
                 $m[$pids[$i]] = "<a href=\"{$url}\">" . $pids[$i] . "</a>";
             }
             ksort($m, SORT_NUMERIC);
             $ls = htmlspecialchars("p/s/" . urlencode("re:" . $row->email));
             return '<div class="has_hotcrp_list" data-hotcrp-list="' . $ls . '">' . join(", ", $m) . '</div>';
         case self::FIELD_TAGS:
             if ($this->contact->isPC && ($tags = $row->viewable_tags($this->contact))) {
                 $x = [];
                 foreach (TagInfo::split($tags) as $t) {
                     $x[] = '<a class="qq nw" href="' . hoturl("users", "t=%23" . TagInfo::base($t)) . '">' . $this->tagger->unparse_hashed($t) . '</a>';
                 }
                 return join(" ", $x);
             }
             return "";
         case self::FIELD_COLLABORATORS:
             if (!$this->contact->isPC || !($row->roles & Contact::ROLE_PC)) {
                 return "";
             }
             $t = array();
             foreach (explode("\n", $row->collaborators) as $collab) {
                 if (preg_match(',\\A(.*?)\\s*(\\(.*\\))\\s*\\z,', $collab, $m)) {
                     $t[] = '<span class="nw">' . htmlspecialchars($m[1]) . ' <span class="auaff">' . htmlspecialchars($m[2]) . '</span></span>';
                 } else {
                     if (($collab = trim($collab)) !== "" && strcasecmp($collab, "None")) {
                         $t[] = '<span class="nw">' . htmlspecialchars($collab) . '</span>';
                     }
                 }
             }
             return join("; ", $t);
         default:
             $f = ReviewForm::field($fieldId);
             if (!$f) {
                 return "";
             }
             if (!($row->roles & Contact::ROLE_PC) && !$this->contact->privChair && $this->limit != "req") {
                 return "";
             }
             $v = scoreCounts($row->{$fieldId}, $this->scoreMax[$fieldId]);
             $m = "";
             if ($v->n > 0) {
                 $m = $f->unparse_graph($v, 2, 0);
             }
             return $m;
     }
 }