public function fetch_comments($where)
 {
     $result = Dbl::qe("select PaperComment.*, firstName reviewFirstName, lastName reviewLastName, email reviewEmail\n            from PaperComment join ContactInfo on (ContactInfo.contactId=PaperComment.contactId)\n            where {$where} order by commentId");
     $comments = array();
     while ($c = CommentInfo::fetch($result, $this)) {
         $comments[$c->commentId] = $c;
     }
     Dbl::free($result);
     return $comments;
 }
assert_search_papers($user_chair, "#fart", "1 2 3 4 5 6 7 8");
assert_search_papers($user_chair, "NOT #fart", "9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30");
assert_search_papers($user_chair, "-#fart", "9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30");
// Check all tags
assert_search_papers($user_chair, "#none", "9 10 11 12 14 15 16 18 19 20 21 22 23 24 25 26 27 28 29 30");
xassert_assign($Admin, false, "paper,tag\n9,~private\n10,~~chair\n");
assert_search_papers($user_chair, "#none", "11 12 14 15 16 18 19 20 21 22 23 24 25 26 27 28 29 30");
assert_search_papers($user_mgbaker, "#none", "3 9 10 11 12 14 15 16 18 19 20 21 22 23 24 25 26 27 28 29 30");
// comment searches
assert_search_papers($user_chair, "cmt:any", "1");
$comment2 = new CommentInfo(null, $paper18);
$c2ok = $comment2->save(array("text" => "test", "visibility" => "a", "blind" => false), $user_mgbaker);
xassert($c2ok);
assert_search_papers($user_chair, "cmt:any", "1 18");
assert_search_papers($user_chair, "cmt:any>1", "");
$comment3 = new CommentInfo(null, $paper18);
$c3ok = $comment3->save(array("text" => "test", "visibility" => "a", "blind" => false, "tags" => "redcmt"), $user_mgbaker);
xassert($c3ok);
assert_search_papers($user_chair, "cmt:any>1", "18");
assert_search_papers($user_chair, "cmt:jon", "");
assert_search_papers($user_chair, "cmt:mgbaker", "1 18");
assert_search_papers($user_chair, "cmt:mgbaker>1", "18");
assert_search_papers($user_chair, "cmt:#redcmt", "18");
/*$result = Dbl::qe("select paperId, tag, tagIndex from PaperTag order by paperId, tag");
$tags = array();
while ($result && ($row = $result->fetch_row()))
    $tags[] = "$row[0],$row[1],$row[2]\n";
echo join("", $tags);*/
// check review visibility for “not unless completed on same paper”
$Conf->save_setting("pc_seeallrev", Conf::PCSEEREV_IFCOMPLETE);
Contact::update_rights();
function save_comment($text, $is_response, $roundnum)
{
    global $Me, $Conf, $prow, $crow;
    if ($crow) {
        $roundnum = (int) $crow->commentRound;
    }
    // If I have a review token for this paper, save under that anonymous user.
    $user = $Me;
    if ((!$crow || $crow->contactId != $Me->contactId) && ($cid = $Me->review_token_cid($prow)) && (!$crow || $crow->contactId == $cid)) {
        $user = Contact::find_by_id($cid);
    }
    $req = array("visibility" => @$_REQUEST["visibility"], "submit" => $is_response && @$_REQUEST["submitresponse"], "text" => $text, "tags" => @$_REQUEST["commenttags"], "blind" => @$_REQUEST["blind"]);
    if ($is_response && !$crow) {
        $cinfo = new CommentInfo((object) array("commentType" => COMMENTTYPE_RESPONSE, "commentRound" => $roundnum), $prow);
    } else {
        $cinfo = new CommentInfo($crow, $prow);
    }
    $ok = $cinfo->save($req, $user);
    $what = $is_response ? "Response" : "Comment";
    $confirm = false;
    if (!$ok && $is_response) {
        $crows = $Conf->comment_rows($Conf->comment_query("paperId={$prow->paperId} and (commentType&" . COMMENTTYPE_RESPONSE . ")!=0 and commentRound={$roundnum}"), $Me);
        reset($crows);
        $cur_response = @current($crows);
        if ($cur_response && $cur_response->comment == $text) {
            $cinfo = new CommentInfo($cur_response, $prow);
            $ok = true;
        } else {
            $confirm = Ht::xmsg("error", "A response was entered concurrently by another user. Reload to see it.");
        }
    }
    if (!$ok) {
        /* nada */
    } else {
        if ($is_response && (!$cinfo->commentId || $cinfo->commentType & COMMENTTYPE_DRAFT)) {
            if ($cinfo->commentId) {
                $confirm = 'Response saved. <strong>This draft response will not be shown to reviewers.</strong>';
            } else {
                $confirm = 'Response deleted.';
            }
            $isuf = $roundnum ? "_{$roundnum}" : "";
            if (($dl = $Conf->printableTimeSetting("resp_done{$isuf}")) != "N/A") {
                $confirm .= " You have until {$dl} to submit the response.";
            }
            $confirm = Ht::xmsg("warning", $confirm);
        } else {
            if ($is_response) {
                $rname = $Conf->resp_round_text($roundnum);
                $confirm = Ht::xmsg("confirm", ($rname ? "{$rname} response" : "Response") . ' submitted.');
            } else {
                if ($cinfo->commentId) {
                    $confirm = Ht::xmsg("confirm", "Comment saved.");
                } else {
                    $confirm = Ht::xmsg("confirm", "Comment deleted.");
                }
            }
        }
    }
    $j = array("ok" => $ok);
    if ($cinfo->commentId) {
        $j["cmt"] = $cinfo->unparse_json($Me);
    }
    if ($confirm) {
        $j["msg"] = $confirm;
    }
    $Conf->ajaxExit($j);
}
 public function save($req, $contact)
 {
     global $Conf, $Now;
     if (is_array($req)) {
         $req = (object) $req;
     }
     $Table = $this->prow->comment_table_name();
     $LinkTable = $this->prow->table_name();
     $LinkColumn = $this->prow->id_column();
     $req_visibility = get($req, "visibility");
     $is_response = !!($this->commentType & COMMENTTYPE_RESPONSE);
     if ($is_response && get($req, "submit")) {
         $ctype = COMMENTTYPE_RESPONSE | COMMENTTYPE_AUTHOR;
     } else {
         if ($is_response) {
             $ctype = COMMENTTYPE_RESPONSE | COMMENTTYPE_AUTHOR | COMMENTTYPE_DRAFT;
         } else {
             if ($req_visibility == "a" || $req_visibility == "au") {
                 $ctype = COMMENTTYPE_AUTHOR;
             } else {
                 if ($req_visibility == "p" || $req_visibility == "pc") {
                     $ctype = COMMENTTYPE_PCONLY;
                 } else {
                     if ($req_visibility == "admin") {
                         $ctype = COMMENTTYPE_ADMINONLY;
                     } else {
                         if ($this->commentId && $req_visibility === null) {
                             $ctype = $this->commentType;
                         } else {
                             // $req->visibility == "r" || $req->visibility == "rev"
                             $ctype = COMMENTTYPE_REVIEWER;
                         }
                     }
                 }
             }
         }
     }
     if ($is_response ? $this->prow->blind : $Conf->is_review_blind(!!get($req, "blind"))) {
         $ctype |= COMMENTTYPE_BLIND;
     }
     // tags
     if ($is_response) {
         $ctags = " response ";
         if (($rname = $Conf->resp_round_name($this->commentRound)) != "1") {
             $ctags .= "{$rname}response ";
         }
     } else {
         if (get($req, "tags") && preg_match_all(',\\S+,', $req->tags, $m)) {
             $tagger = new Tagger($contact);
             $ctags = array();
             foreach ($m[0] as $text) {
                 if (($text = $tagger->check($text, Tagger::NOVALUE)) && !stri_ends_with($text, "response")) {
                     $ctags[strtolower($text)] = $text;
                 }
             }
             $tagger->sort($ctags);
             $ctags = count($ctags) ? " " . join(" ", $ctags) . " " : null;
         } else {
             $ctags = null;
         }
     }
     // notifications
     $displayed = !($ctype & COMMENTTYPE_DRAFT);
     // query
     $text = get_s($req, "text");
     $q = "";
     $qv = array();
     if ($text === "" && $this->commentId) {
         $change = true;
         $q = "delete from {$Table} where commentId={$this->commentId}";
     } else {
         if ($text === "") {
             /* do nothing */
         } else {
             if (!$this->commentId) {
                 $change = true;
                 $qa = ["contactId, {$LinkColumn}, commentType, comment, commentOverflow, timeModified, replyTo"];
                 $qb = [$contact->contactId, $this->prow->{$LinkColumn}, $ctype, "?", "?", $Now, 0];
                 if (strlen($text) <= 32000) {
                     array_push($qv, $text, null);
                 } else {
                     array_push($qv, UnicodeHelper::utf8_prefix($text, 200), $text);
                 }
                 if ($ctags !== null) {
                     $qa[] = "commentTags";
                     $qb[] = "?";
                     $qv[] = $ctags;
                 }
                 if ($is_response) {
                     $qa[] = "commentRound";
                     $qb[] = $this->commentRound;
                 }
                 if ($displayed) {
                     $qa[] = "timeDisplayed, timeNotified";
                     $qb[] = "{$Now}, {$Now}";
                 }
                 $q = "insert into {$Table} (" . join(", ", $qa) . ") select " . join(", ", $qb) . "\n";
                 if ($is_response) {
                     // make sure there is exactly one response
                     $q .= " from (select {$LinkTable}.{$LinkColumn}, coalesce(commentId, 0) commentId\n                from {$LinkTable}\n                left join {$Table} on ({$Table}.{$LinkColumn}={$LinkTable}.{$LinkColumn} and (commentType&" . COMMENTTYPE_RESPONSE . ")!=0 and commentRound={$this->commentRound})\n                where {$LinkTable}.{$LinkColumn}={$this->prow->{$LinkColumn}} limit 1) t\n        where t.commentId=0";
                 }
             } else {
                 $change = $this->commentType >= COMMENTTYPE_AUTHOR != $ctype >= COMMENTTYPE_AUTHOR;
                 if ($this->timeModified >= $Now) {
                     $Now = $this->timeModified + 1;
                 }
                 // do not notify on updates within 3 hours
                 $qa = "";
                 if ($this->timeNotified + 10800 < $Now || $ctype & COMMENTTYPE_RESPONSE && !($ctype & COMMENTTYPE_DRAFT) && $this->commentType & COMMENTTYPE_DRAFT) {
                     $qa .= ", timeNotified={$Now}";
                 }
                 // reset timeDisplayed if you change the comment type
                 if ((!$this->timeDisplayed || $this->ordinal_missing($ctype)) && $text !== "" && $displayed) {
                     $qa .= ", timeDisplayed={$Now}";
                 }
                 $q = "update {$Table} set timeModified={$Now}{$qa}, commentType={$ctype}, comment=?, commentOverflow=?, commentTags=? where commentId={$this->commentId}";
                 if (strlen($text) <= 32000) {
                     array_push($qv, $text, null);
                 } else {
                     array_push($qv, UnicodeHelper::utf8_prefix($text, 200), $text);
                 }
                 $qv[] = $ctags;
             }
         }
     }
     $result = Dbl::qe_apply($q, $qv);
     if (!$result) {
         return false;
     }
     $cmtid = $this->commentId ?: $result->insert_id;
     if (!$cmtid) {
         return false;
     }
     // log
     $contact->log_activity("Comment {$cmtid} " . ($text !== "" ? "saved" : "deleted"), $this->prow->{$LinkColumn});
     // ordinal
     if ($text !== "" && $this->ordinal_missing($ctype)) {
         $this->save_ordinal($cmtid, $ctype, $Table, $LinkTable, $LinkColumn);
     }
     // reload
     if ($text !== "") {
         $comments = $this->prow->fetch_comments("commentId={$cmtid}");
         $this->merge($comments[$cmtid], $this->prow);
         if ($this->timeNotified == $this->timeModified) {
             self::$watching = $this;
             $this->prow->notify(WATCHTYPE_COMMENT, "CommentInfo::watch_callback", $contact);
             self::$watching = null;
         }
     } else {
         $this->commentId = 0;
         $this->comment = "";
         $this->commentTags = null;
     }
     return true;
 }
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 run(Contact $user, $qreq, $ssel)
 {
     global $Conf;
     $result = Dbl::qe_raw($Conf->paperQuery($user, array("paperId" => $ssel->selection(), "allReviews" => 1, "reviewerName" => 1)));
     $texts = array();
     $errors = array();
     $user->set_forceShow(true);
     $rf = ReviewForm::get();
     while ($row = PaperInfo::fetch($result, $user)) {
         if ($whyNot = $user->perm_view_review($row, null, null)) {
             $errors[whyNotText($whyNot, "view review")] = true;
         } else {
             if ($row->reviewSubmitted) {
                 defappend($texts[$row->paperId], $rf->pretty_text($row, $row, $user) . "\n");
             }
         }
     }
     $crows = $Conf->comment_rows($Conf->paperQuery($user, array("paperId" => $ssel->selection(), "allComments" => 1, "reviewerName" => 1)), $user);
     foreach ($crows as $row) {
         if ($user->can_view_comment($row, $row, null)) {
             $crow = new CommentInfo($row, $row);
             defappend($texts[$row->paperId], $crow->unparse_text($user) . "\n");
         }
     }
     $this->finish($ssel, $texts, $errors);
 }
    } else {
        json_exit(["ok" => false]);
    }
}
if ($qreq->fn === "events" && $Me->is_reviewer()) {
    $from = $qreq->from;
    if (!$from || !ctype_digit($from)) {
        $from = $Now;
    }
    $entries = $Conf->reviewerActivity($Me, $from, 10);
    $when = $from;
    $rows = array();
    $rf = ReviewForm::get();
    foreach ($entries as $which => $xr) {
        if ($xr->isComment) {
            $rows[] = CommentInfo::unparse_flow_entry($xr, $Me, "");
            $when = $xr->timeModified;
        } else {
            $rows[] = $rf->reviewFlowEntry($Me, $xr, "");
            $when = $xr->reviewSubmitted;
        }
    }
    json_exit(["ok" => true, "from" => (int) $from, "to" => (int) $when - 1, "rows" => $rows]);
} else {
    if ($qreq->fn === "events") {
        json_exit(["ok" => false]);
    }
}
if ($qreq->fn === "searchcompletion") {
    $s = new PaperSearch($Me, "");
    $Conf->ajaxExit(array("ok" => true, "searchcompletion" => $s->search_completion()));