コード例 #1
0
 public function __construct($user, $rounds = null)
 {
     global $Conf;
     $this->contact = $user;
     $qp = "select PaperReview.contactId, timeRequested, reviewSubmitted, reviewRound";
     if (!$this->contact->privChair) {
         $qp .= ", conflictType from PaperReview left join PaperConflict on (PaperConflict.paperId=PaperReview.paperId and PaperConflict.contactId=" . $this->contact->contactId . ")";
     } else {
         $qp .= ", 0 conflictType from PaperReview";
     }
     $qp .= " where reviewType>" . REVIEW_PC . " or (reviewType=" . REVIEW_PC . " and timeRequested>0 and reviewSubmitted>0)";
     if (!$this->contact->privChair) {
         $qp .= " and coalesce(conflictType,0)=0";
     }
     $qa = array();
     if ($rounds) {
         $qp .= " and reviewRound ?a";
         $qa[] = $rounds;
     }
     $result = Dbl::qe_apply($qp, $qa);
     while ($row = edb_row($result)) {
         $cid = (int) $row[4] ? "conflicts" : (int) $row[0];
         $this->r[$cid][] = array((int) $row[1], (int) $row[2], (int) $row[3]);
     }
     Dbl::free($result);
     foreach ($Conf->round_list() as $rn => $r) {
         $dl = $Conf->review_deadline($rn, true, false);
         $this->dl[$rn] = +$Conf->setting($dl);
     }
     // maybe hide who's who
     if (!$this->contact->can_view_aggregated_review_identity()) {
         $who = $r = array();
         foreach ($this->r as $cid => $data) {
             if ($cid === "conflicts" || $cid == $this->contact->contactId) {
                 $r[$cid] = $data;
             } else {
                 do {
                     $ncid = mt_rand(1, 10 * count(pcMembers()));
                 } while (isset($who[$ncid]));
                 $who[$ncid] = true;
                 $r["x" . $ncid] = $data;
             }
         }
         $this->r = $r;
     }
 }
コード例 #2
0
ファイル: profile.php プロジェクト: benesch/peteramati
}
if (@$_POST["update"] && check_post()) {
    $ck = $cv = array();
    $roles = 0;
    if (@$_POST["pctype"] === "chair") {
        $roles |= Contact::ROLE_CHAIR | Contact::ROLE_PC;
    } else {
        if (@$_POST["pctype"] === "pc") {
            $roles |= Contact::ROLE_PC;
        }
    }
    if (@$_POST["sysadmin"]) {
        $roles |= Contact::ROLE_ADMIN;
    }
    $ck[] = "roles={$roles}";
    Dbl::qe_apply("update ContactInfo set " . join($ck, ",") . " where contactId=" . $User->contactId, $cv);
    redirectSelf();
}
$Conf->header("Profile", "profile");
$xsep = " <span class='barsep'>&nbsp;|&nbsp;</span> ";
echo "<div id='homeinfo'>";
echo "<h2 class='homeemail'>", Text::user_html($User), "</h2>";
if ($User->seascode_username || $User->huid) {
    echo '<h3><a href="', hoturl("index", array("u" => $Me->user_linkpart($User))), '">', htmlspecialchars($User->seascode_username ?: $User->huid), '</a>';
    if ($Me->privChair) {
        echo "&nbsp;", become_user_link($User);
    }
    echo "</h3>";
}
if ($User->dropped) {
    ContactView::echo_group("", '<strong class="err">You have dropped the course.</strong> If this is incorrect, contact us.');
コード例 #3
0
 function save_json($cj, $actor, $send)
 {
     global $Conf, $Me, $Now;
     $inserting = !$this->contactId;
     $old_roles = $this->roles;
     $old_email = $this->email;
     $different_email = strtolower($cj->email) !== strtolower((string) $old_email);
     $cu = new Contact_Update($inserting, $different_email);
     $aupapers = null;
     if ($different_email) {
         $aupapers = self::email_authored_papers($cj->email, $cj);
     }
     // check whether this user is changing themselves
     $changing_other = false;
     if (self::contactdb() && $Me && (strcasecmp($this->email, $Me->email) != 0 || $Me->is_actas_user())) {
         $changing_other = true;
     }
     // Main fields
     foreach (array("firstName", "lastName", "email", "affiliation", "collaborators", "preferredEmail", "country") as $k) {
         if (isset($cj->{$k})) {
             $this->_save_assign_field($k, $cj->{$k}, $cu);
         }
     }
     if (isset($cj->phone)) {
         $this->_save_assign_field("voicePhoneNumber", $cj->phone, $cu);
     }
     $this->_save_assign_field("unaccentedName", Text::unaccented_name($this->firstName, $this->lastName), $cu);
     self::set_sorter($this);
     // Disabled
     $disabled = $this->disabled ? 1 : 0;
     if (isset($cj->disabled)) {
         $disabled = $cj->disabled ? 1 : 0;
     }
     if (($this->disabled ? 1 : 0) !== $disabled || !$this->contactId) {
         $cu->qv["disabled"] = $this->disabled = $disabled;
     }
     // Data
     $old_datastr = $this->data_str();
     $data = (object) array();
     foreach (array("address", "city", "state", "zip") as $k) {
         if (isset($cj->{$k}) && ($x = $cj->{$k})) {
             while (is_array($x) && $x[count($x) - 1] === "") {
                 array_pop($x);
             }
             $data->{$k} = $x ?: null;
         }
     }
     $this->merge_data($data);
     $datastr = $this->data_str();
     if ($datastr !== $old_datastr) {
         $cu->qv["data"] = $datastr;
     }
     // Changes to the above fields also change the updateTime.
     if (count($cu->qv)) {
         $cu->qv["updateTime"] = $this->updateTime = $Now;
     }
     // Follow
     if (isset($cj->follow)) {
         $w = 0;
         if (get($cj->follow, "reviews")) {
             $w |= WATCH_COMMENT;
         }
         if (get($cj->follow, "allreviews")) {
             $w |= WATCH_ALLCOMMENTS;
         }
         if (get($cj->follow, "allfinal")) {
             $w |= WATCHTYPE_FINAL_SUBMIT << WATCHSHIFT_ALL;
         }
         $this->_save_assign_field("defaultWatch", $w, $cu);
     }
     // Tags
     if (isset($cj->tags)) {
         $tags = array();
         foreach ($cj->tags as $t) {
             list($tag, $value) = TagInfo::split_index($t);
             if (strcasecmp($tag, "pc") != 0) {
                 $tags[$tag] = $tag . "#" . ($value ?: 0);
             }
         }
         ksort($tags);
         $t = count($tags) ? " " . join(" ", $tags) . " " : "";
         $this->_save_assign_field("contactTags", $t, $cu);
     }
     // If inserting, set initial password and creation time
     if ($inserting) {
         $cu->qv["creationTime"] = $this->creationTime = $Now;
         $this->_create_password(self::contactdb_find_by_email($this->email), $cu);
     }
     // Initial save
     if (count($cu->qv)) {
         // always true if $inserting
         $q = ($inserting ? "insert into" : "update") . " ContactInfo set " . join("=?, ", array_keys($cu->qv)) . "=?" . ($inserting ? "" : " where contactId={$this->contactId}");
         if (!($result = Dbl::qe_apply($Conf->dblink, $q, array_values($cu->qv)))) {
             return $result;
         }
         if ($inserting) {
             $this->contactId = $this->cid = (int) $result->insert_id;
         }
         Dbl::free($result);
     }
     // Topics
     if (isset($cj->topics)) {
         $tf = array();
         foreach ($cj->topics as $k => $v) {
             $tf[] = "({$this->contactId},{$k},{$v})";
         }
         $Conf->qe("delete from TopicInterest where contactId={$this->contactId}");
         if (count($tf)) {
             $Conf->qe("insert into TopicInterest (contactId,topicId,interest) values " . join(",", $tf));
         }
     }
     // Roles
     $roles = 0;
     if (isset($cj->roles)) {
         $roles = self::parse_roles_json($cj->roles);
         if ($roles !== $old_roles) {
             $this->save_roles($roles, $actor);
         }
     }
     // Update authorship
     if ($aupapers) {
         $this->save_authored_papers($aupapers);
     }
     // Update contact database
     $cdbu = $this->contactDbId ? $this : $this->contactdb_user_;
     if ($different_email) {
         $cdbu = null;
     }
     if (($cdb = self::contactdb()) && (!$cdbu || count($cu->cdb_uqv))) {
         $qv = [];
         if (!$cdbu) {
             $q = "insert into ContactInfo set firstName=?, lastName=?, email=?, affiliation=?, country=?, collaborators=?";
             $qv = array($this->firstName, $this->lastName, $this->email, $this->affiliation, $this->country, $this->collaborators);
             if ($this->password !== "" && ($this->password[0] !== " " || $this->password[1] === "\$")) {
                 $q .= ", password=?";
                 $qv[] = $this->password;
             }
             $q .= " on duplicate key update ";
         } else {
             $q = "update ContactInfo set ";
         }
         if (count($cu->cdb_uqv) && $changing_other) {
             $q .= join(", ", array_map(function ($k) {
                 return "{$k}=if(coalesce({$k},'')='',?,{$k})";
             }, array_keys($cu->cdb_uqv)));
         } else {
             if (count($cu->cdb_uqv)) {
                 $q .= join("=?, ", array_keys($cu->cdb_uqv)) . "=?";
             } else {
                 $q .= "firstName=firstName";
             }
         }
         if (count($cu->cdb_uqv)) {
             $q .= ", updateTime={$Now}";
         }
         $qv = array_merge($qv, array_values($cu->cdb_uqv));
         if ($cdbu) {
             $q .= " where contactDbId=" . $cdbu->contactDbId;
         }
         $result = Dbl::ql_apply($cdb, $q, $qv);
         Dbl::free($result);
         $this->contactdb_user_ = false;
     }
     // Password
     if (isset($cj->new_password)) {
         $this->change_password(get($cj, "old_password"), $cj->new_password, 0);
     }
     // Beware PC cache
     if (($roles | $old_roles) & Contact::ROLE_PCLIKE) {
         $Conf->invalidateCaches(array("pc" => 1));
     }
     // Mark creation and activity
     if ($inserting) {
         if ($send && !$this->disabled) {
             $this->sendAccountInfo("create", false);
         }
         $type = $this->disabled ? "disabled " : "";
         if ($Me && $Me->has_email() && $Me->email !== $this->email) {
             $Conf->log("Created {$type}account ({$Me->email})", $this);
         } else {
             $Conf->log("Created {$type}account", $this);
         }
     }
     $actor = $actor ?: $Me;
     if ($actor && $this->contactId == $actor->contactId) {
         $this->mark_activity();
     }
     return true;
 }
コード例 #4
0
function do_setting_update($sv)
{
    global $Conf, $Group, $Me, $Now, $Opt, $OptOverride;
    // parse settings
    foreach (Si::$all as $si) {
        account_value($sv, $si);
    }
    // check date relationships
    foreach (array("sub_reg" => "sub_sub", "final_soft" => "final_done") as $dn1 => $dn2) {
        list($dv1, $dv2) = [$sv->savedv($dn1), $sv->savedv($dn2)];
    }
    if (!$dv1 && $dv2) {
        $sv->save($dn1, $dv2);
    } else {
        if ($dv2 && $dv1 > $dv2) {
            $sv->set_error($dn1, unparse_setting_error(Si::get($dn1), "Must come before " . Si::get($dn2, "short_description") . "."));
            $sv->set_error($dn2);
        }
    }
    if ($sv->has_savedv("sub_sub")) {
        $sv->save("sub_update", $sv->savedv("sub_sub"));
    }
    if (get($Opt, "defaultSiteContact")) {
        if ($sv->has_savedv("opt.contactName") && get($Opt, "contactName") === $sv->savedv("opt.contactName")) {
            $sv->save("opt.contactName", null);
        }
        if ($sv->has_savedv("opt.contactEmail") && get($Opt, "contactEmail") === $sv->savedv("opt.contactEmail")) {
            $sv->save("opt.contactEmail", null);
        }
    }
    if ($sv->has_savedv("resp_active") && $sv->savedv("resp_active")) {
        foreach (explode(" ", $sv->newv("resp_rounds")) as $i => $rname) {
            $isuf = $i ? "_{$i}" : "";
            if ($sv->newv("resp_open{$isuf}") > $sv->newv("resp_done{$isuf}")) {
                $sv->set_error("resp_open{$isuf}", unparse_setting_error(Si::get("resp_open"), "Must come before " . Si::get("resp_done", "short_description") . "."));
                $sv->set_error("resp_done{$isuf}");
            }
        }
    }
    // update 'papersub'
    if ($sv->has_savedv("pc_seeall")) {
        // see also conference.php
        if ($sv->savedv("pc_seeall") <= 0) {
            $x = "timeSubmitted>0";
        } else {
            $x = "timeWithdrawn<=0";
        }
        $num = Dbl::fetch_ivalue("select paperId from Paper where {$x} limit 1") ? 1 : 0;
        if ($num != $Conf->setting("papersub")) {
            $sv->save("papersub", $num);
        }
    }
    // Setting relationships
    if ($sv->has_savedv("sub_open") && $sv->newv("sub_open", 1) <= 0 && $sv->oldv("sub_open") > 0 && $sv->newv("sub_sub") <= 0) {
        $sv->save("sub_close", $Now);
    }
    if ($sv->has_savedv("msg.clickthrough_submit")) {
        $sv->save("clickthrough_submit", null);
    }
    // make settings
    $changedn = [];
    if (!$sv->has_errors() && (count($sv->savedv) || count($sv->save_callbacks))) {
        $tables = "Settings write";
        foreach ($sv->need_lock as $t => $need) {
            if ($need) {
                $tables .= ", {$t} write";
            }
        }
        $Conf->qe("lock tables {$tables}");
        // load db settings, pre-crosscheck
        $dbsettings = array();
        $result = Dbl::qe("select name, value, data from Settings");
        while ($row = edb_row($result)) {
            $dbsettings[$row[0]] = $row;
        }
        Dbl::free($result);
        // apply settings
        foreach ($sv->save_callbacks as $si) {
            $p = $sv->parser($si);
            $p->save($sv, $si);
        }
        $dv = $aq = $av = array();
        foreach ($sv->savedv as $n => $v) {
            if (substr($n, 0, 4) === "opt." && $v !== null) {
                $okey = substr($n, 4);
                $oldv = array_key_exists($okey, $OptOverride) ? $OptOverride[$okey] : get($Opt, $okey);
                $Opt[$okey] = $v[1] === null ? $v[0] : $v[1];
                if ($oldv === $Opt[$okey]) {
                    $v = null;
                } else {
                    if (!array_key_exists($okey, $OptOverride)) {
                        $OptOverride[$okey] = $oldv;
                    }
                }
            }
            if ($v === null ? !isset($dbsettings[$n]) : isset($dbsettings[$n]) && (int) $dbsettings[$n][1] === $v[0] && $dbsettings[$n][2] === $v[1]) {
                continue;
            }
            $changedn[] = $n;
            if ($v !== null) {
                $aq[] = "(?, ?, ?)";
                array_push($av, $n, $v[0], $v[1]);
            } else {
                $dv[] = $n;
            }
        }
        if (count($dv)) {
            Dbl::qe_apply("delete from Settings where name?a", array($dv));
            //Conf::msg_info(Ht::pre_text_wrap(Dbl::format_query_apply("delete from Settings where name?a", array($dv))));
        }
        if (count($aq)) {
            Dbl::qe_apply("insert into Settings (name, value, data) values\n\t" . join(",\n\t", $aq) . "\n\ton duplicate key update value=values(value), data=values(data)", $av);
            //Conf::msg_info(Ht::pre_text_wrap(Dbl::format_query_apply("insert into Settings (name, value, data) values\n\t" . join(",\n\t", $aq) . "\n\ton duplicate key update value=values(value), data=values(data)", $av)));
        }
        $Conf->qe("unlock tables");
        if (count($changedn)) {
            $Me->log_activity("Updated settings " . join(", ", $changedn));
        }
        $Conf->load_settings();
        // contactdb may need to hear about changes to shortName
        if ($sv->has_savedv("opt.shortName") && get($Opt, "contactdb_dsn") && ($cdb = Contact::contactdb())) {
            Dbl::ql($cdb, "update Conferences set shortName=? where dbName=?", $Opt["shortName"], $Opt["dbName"]);
        }
    }
    // update the review form in case it's changed
    ReviewForm::clear_cache();
    if (!$sv->has_errors()) {
        $Conf->save_session("settings_highlight", $sv->error_fields());
        if (count($changedn)) {
            $Conf->confirmMsg("Changes saved.");
        } else {
            $Conf->warnMsg("No changes.");
        }
        $sv->report();
        redirectSelf();
    } else {
        SettingGroup::crosscheck($sv, $Group);
        $sv->report();
    }
}
コード例 #5
0
 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;
 }
コード例 #6
0
 static function alltags_api($user, $qreq, $prow)
 {
     global $Conf;
     if (!$user->isPC) {
         json_exit(["ok" => false]);
     }
     $need_paper = $conflict_where = false;
     $where = $args = array();
     if ($user->allow_administer(null)) {
         $need_paper = true;
         if ($Conf->has_any_manager() && !$Conf->setting("tag_seeall")) {
             $conflict_where = "(p.managerContactId=0 or p.managerContactId={$user->contactId} or pc.conflictType is null)";
         }
     } else {
         if ($Conf->check_track_sensitivity(Track::VIEW)) {
             $where[] = "t.paperId ?a";
             $args[] = $user->list_submitted_papers_with_viewable_tags();
         } else {
             $need_paper = true;
             if ($Conf->has_any_manager() && !$Conf->setting("tag_seeall")) {
                 $conflict_where = "(p.managerContactId={$user->contactId} or pc.conflictType is null)";
             } else {
                 if (!$Conf->setting("tag_seeall")) {
                     $conflict_where = "pc.conflictType is null";
                 }
             }
         }
     }
     $q = "select distinct tag from PaperTag t";
     if ($need_paper) {
         $q .= " join Paper p on (p.paperId=t.paperId)";
         $where[] = "p.timeSubmitted>0";
     }
     if ($conflict_where) {
         $q .= " left join PaperConflict pc on (pc.paperId=t.paperId and pc.contactId={$user->contactId})";
         $where[] = $conflict_where;
     }
     $q .= " where " . join(" and ", $where);
     $tags = array();
     $result = Dbl::qe_apply($q, $args);
     while ($row = edb_row($result)) {
         $twiddle = strpos($row[0], "~");
         if ($twiddle === false || $twiddle == 0 && $row[0][1] === "~" && $user->privChair) {
             $tags[] = $row[0];
         } else {
             if ($twiddle > 0 && substr($row[0], 0, $twiddle) == $user->contactId) {
                 $tags[] = substr($row[0], $twiddle);
             }
         }
     }
     Dbl::free($result);
     json_exit(["ok" => true, "tags" => $tags]);
 }