Skip to content

Commit

Permalink
The copytag/renametag assignment actions also copy/rename tag annotat…
Browse files Browse the repository at this point in the history
…ions.
  • Loading branch information
kohler committed Sep 8, 2023
1 parent d9a1d6e commit 72d7d1e
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 49 deletions.
1 change: 1 addition & 0 deletions batch/makedist.sh
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ src/assigners/a_preference.php
src/assigners/a_review.php
src/assigners/a_status.php
src/assigners/a_tag.php
src/assigners/a_taganno.php
src/assigners/a_unsubmitreview.php
src/assignmentcountset.php
src/assignmentset.php
Expand Down
52 changes: 40 additions & 12 deletions src/assigners/a_copytag.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ function __construct(Conf $conf, $aj) {
$this->move = $aj->move ?? false;
}
function load_state(AssignmentState $state) {
Tag_AssignmentParser::load_tag_state($state);
Tag_Assignable::load($state);
TagAnno_Assignable::load($state);
}
function paper_universe($req, AssignmentState $state) {
return "reqpost";
}
function allow_paper(PaperInfo $prow, AssignmentState $state) {
if (($whyNot = $state->user->perm_edit_some_tag($prow))) {
if ($prow->paperId > 0
&& ($whyNot = $state->user->perm_edit_some_tag($prow))) {
return new AssignmentError($whyNot);
} else {
return true;
Expand All @@ -35,6 +40,7 @@ function apply(PaperInfo $prow, Contact $contact, $req, AssignmentState $state)
$state->error($tagger->error_ftext(true));
return false;
}
$ltag = strtolower($tag);
$new_tag = $tagger->check($req["new_tag"] ?? "", Tagger::NOVALUE);
if (!$new_tag) {
if ($tagger->error_code() === Tagger::EEMPTY) {
Expand All @@ -46,7 +52,8 @@ function apply(PaperInfo $prow, Contact $contact, $req, AssignmentState $state)
}

// if you can't view the tag, you can't copy or move the tag
if (!$state->user->can_view_tag($prow, $tag)) {
if ($prow->paperId > 0
&& !$state->user->can_view_tag($prow, $tag)) {
return Tag_AssignmentParser::cannot_view_error($prow, $tag, $state);
}

Expand All @@ -60,18 +67,39 @@ function apply(PaperInfo $prow, Contact $contact, $req, AssignmentState $state)
return false;
}

$ltag = strtolower($tag);
$res = $state->query(new Tag_Assignable($prow->paperId, $ltag));
if (!$res) {
return true;
// real paper: change/move tag
if ($prow->paperId > 0) {
$res = $state->query(new Tag_Assignable($prow->paperId, $ltag));
if (!$res) {
return true;
}
assert(count($res) === 1);

$lnew_tag = strtolower($new_tag);
$state->add(new Tag_Assignable($prow->paperId, $lnew_tag, $new_tag, $res[0]->_index));
if ($this->move) {
$state->remove($res[0]);
}
}
assert(count($res) === 1);

$lnew_tag = strtolower($new_tag);
$state->add(new Tag_Assignable($prow->paperId, $lnew_tag, $new_tag, $res[0]->_index));
if ($this->move) {
$state->remove($res[0]);
// placeholder: change/move tag annotations
if ($prow->paperId < 0
&& $state->user->can_edit_tag_anno($tag)
&& $state->user->can_edit_tag_anno($new_tag)
&& ($ares = $state->query(new TagAnno_Assignable($tag, null)))) {
if (!$state->query(new TagAnno_Assignable($new_tag, null))) {
foreach ($ares as $taa) {
$state->add($taa->with_tag($new_tag));
}
}
if ($this->move
&& !$state->query(new Tag_Assignable(null, $ltag))) {
foreach ($ares as $taa) {
$x = $state->remove($taa);
}
}
}

return true;
}
}
2 changes: 1 addition & 1 deletion src/assigners/a_status.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ function apply(PaperInfo $prow, Contact $contact, $req, AssignmentState $state)
$res->_withdrawn = Conf::$now;
$res->_submitted = -$res->_submitted;
if ($state->conf->tags()->has(TagInfo::TFM_VOTES)) {
Tag_AssignmentParser::load_tag_state($state);
Tag_Assignable::load($state);
$state->register_preapply_function("withdraw {$prow->paperId}", new Withdraw_PreapplyFunction($prow->paperId));
}
}
Expand Down
24 changes: 12 additions & 12 deletions src/assigners/a_tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ function match($q) {
&& ($q->ltag ?? $this->ltag) === $this->ltag
&& ($q->_index ?? $this->_index) === $this->_index;
}
static function load(AssignmentState $state) {
if (!$state->mark_type("tag", ["pid", "ltag"], "Tag_Assigner::make")) {
return;
}
$result = $state->conf->qe("select paperId, tag, tagIndex from PaperTag where paperId?a", $state->paper_ids());
while (($row = $result->fetch_row())) {
$state->load(new Tag_Assignable(+$row[0], strtolower($row[1]), $row[1], (float) $row[2]));
}
Dbl::free($result);
}
}

class NextTagAssigner implements AssignmentPreapplyFunction {
Expand Down Expand Up @@ -78,7 +88,7 @@ function preapply(AssignmentState $state) {
$ltag = strtolower($this->tag);
foreach ($this->pidindex as $pid => $index) {
if ($index >= $this->first_index && $index < $this->next_index) {
$x = $state->query_unmodified(new Tag_Assignable($pid, $ltag));
$x = $state->query_unedited(new Tag_Assignable($pid, $ltag));
if (!empty($x)) {
$state->add(new Tag_Assignable($pid, $ltag, $this->tag, $this->next_index($this->isseq), true));
}
Expand Down Expand Up @@ -110,18 +120,8 @@ function __construct(Conf $conf, $aj) {
function expand_papers($req, AssignmentState $state) {
return $this->itype ? "ALL" : (string) $req["paper"];
}
static function load_tag_state(AssignmentState $state) {
if (!$state->mark_type("tag", ["pid", "ltag"], "Tag_Assigner::make")) {
return;
}
$result = $state->conf->qe("select paperId, tag, tagIndex from PaperTag where paperId?a", $state->paper_ids());
while (($row = $result->fetch_row())) {
$state->load(new Tag_Assignable(+$row[0], strtolower($row[1]), $row[1], (float) $row[2]));
}
Dbl::free($result);
}
function load_state(AssignmentState $state) {
self::load_tag_state($state);
Tag_Assignable::load($state);
}
function allow_paper(PaperInfo $prow, AssignmentState $state) {
if (($whyNot = $state->user->perm_edit_some_tag($prow))) {
Expand Down
91 changes: 91 additions & 0 deletions src/assigners/a_taganno.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
// a_taganno.php -- HotCRP assignment helper classes
// Copyright (c) 2006-2023 Eddie Kohler; see LICENSE.

class TagAnno_Assignable extends Assignable {
/** @var string */
public $ltag;
/** @var int */
public $annoId;
/** @var string */
public $_tag;
/** @var ?float */
public $_tagIndex;
/** @var ?string */
public $_heading;
/** @var ?string */
public $_infoJson;
/** @param string $tag
* @param int $annoId
* @param ?float $index
* @param ?string $heading
* @param ?string $infoJson */
function __construct($tag, $annoId, $index = null, $heading = null, $infoJson = null) {
$this->type = "taganno";
$this->pid = 0;
$this->ltag = strtolower($tag);
$this->annoId = $annoId;
$this->_tag = $tag;
$this->_tagIndex = $index;
$this->_heading = $heading;
$this->_infoJson = $infoJson;
}
/** @return self */
function fresh() {
return new TagAnno_Assignable($this->_tag, $this->annoId);
}
/** @param string $t
* @return self */
function with_tag($t) {
$x = clone $this;
$x->ltag = strtolower($t);
$x->_tag = $t;
return $x;
}
/** @param Assignable $q
* @return bool */
function match($q) {
'@phan-var-force TagAnno_Assignable $q';
return ($q->ltag ?? $this->ltag) === $this->ltag
&& ($q->annoId ?? $this->annoId) === $this->annoId;
}
static function load(AssignmentState $state) {
if (!$state->mark_type("taganno", ["ltag", "annoId"], "TagAnno_Assigner::make")) {
return;
}
$result = $state->conf->qe("select tag, annoId, tagIndex, heading, infoJson from PaperTagAnno");
while (($row = $result->fetch_row())) {
$state->load(new TagAnno_Assignable($row[0], +$row[1], +$row[2], $row[3], $row[4]));
}
Dbl::free($result);
}
}

class TagAnno_Assigner extends Assigner {
function __construct(AssignmentItem $item, AssignmentState $state) {
parent::__construct($item, $state);
}
static function make(AssignmentItem $item, AssignmentState $state) {
if (!$state->user->can_edit_tag_anno($item["ltag"])) {
throw new AssignmentError("<0>You can’t edit this tag’s annotations");
}
return new TagAnno_Assigner($item, $state);
}
function unparse_description() {
return "tag annotation";
}
function add_locks(AssignmentSet $aset, &$locks) {
$locks["PaperTagAnno"] = "write";
}
function execute(AssignmentSet $aset) {
if ($this->item->deleted()) {
$aset->stage_qe("delete from PaperTagAnno where tag=? and annoId=?",
$this->item->pre("_tag"), $this->item->pre("annoId"));
} else {
$aset->stage_qe("insert into PaperTagAnno set tag=?, annoId=?, tagIndex=?, heading=?, infoJson=? ?U on duplicate key update tagIndex=?U(tagIndex), heading=?U(heading), infoJson=?U(infoJson)",
$this->item->pre("_tag"), $this->item->pre("annoId"),
$this->item->post("_tagIndex"), $this->item->post("_heading"),
$this->item->post("_infoJson"));
}
}
}
58 changes: 34 additions & 24 deletions src/assignmentset.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,16 @@ function deleted() {
return $this->deleted;
}
/** @return bool */
function modified() {
function edited() {
return !!$this->after;
}
/** @return bool */
function changed() {
return $this->after
&& ($this->deleted
? $this->existed
: !$this->existed || !$this->after->match($this->before));
}
/** @param bool $pre
* @param string $offset */
function get($pre, $offset) {
Expand Down Expand Up @@ -324,10 +331,10 @@ function query($q) {
/** @template T
* @param T $q
* @return list<T> */
function query_unmodified($q) {
function query_unedited($q) {
$res = [];
foreach ($this->query_items($q) as $item) {
if (!$item->modified())
if (!$item->edited())
$res[] = $item->before;
}
return $res;
Expand Down Expand Up @@ -387,8 +394,7 @@ function diff() {
$diff = [];
foreach ($this->st as $pid => $st) {
foreach ($st->items as $item) {
if ($item->after
&& (!$item->existed() || !$item->after->match($item->before)))
if ($item->changed())
$diff[$pid][] = $item;
}
}
Expand Down Expand Up @@ -737,11 +743,13 @@ function __construct($type) {
$this->type = $type;
}
// Return a descriptor of the set of papers relevant for this action.
// Returns `""` or `"none"`.
// `"req"`, the default, means call `apply` for each requested paper.
// `"none"` means call `apply` exactly once with a placeholder paper.
// `"reqpost"` means each requested paper, then once with a placeholder.
/** @param CsvRow $req
* @return ''|'none' */
* @return 'req'|'none'|'reqpost' */
function paper_universe($req, AssignmentState $state) {
return "";
return "req";
}
// Optionally expand the set of interesting papers. Returns a search
// expression, such as "ALL", or false.
Expand Down Expand Up @@ -1608,25 +1616,27 @@ private function apply_req(AssignmentParser $aparser = null, $req) {
$this->astate->paper_exact_match = $pfield_straight;

// check conflicts and perform assignment
if ($paper_universe === "none") {
$prow = $this->astate->placeholder_prow();
$any_success = $this->apply_paper($prow, $contacts, $aparser, $req) === 1;
} else {
$any_success = false;
foreach ($pids as $p) {
$prow = $this->astate->prow($p);
if (!$prow) {
$this->error("<5>" . $this->user->no_paper_whynot($p)->unparse_html());
} else {
$ret = $this->apply_paper($prow, $contacts, $aparser, $req);
if ($ret === 1) {
$any_success = true;
} else if ($ret < 0) {
break;
}
$any_success = false;
foreach ($pids as $p) {
$prow = $this->astate->prow($p);
if (!$prow) {
$this->error("<5>" . $this->user->no_paper_whynot($p)->unparse_html());
} else {
$ret = $this->apply_paper($prow, $contacts, $aparser, $req);
if ($ret === 1) {
$any_success = true;
} else if ($ret < 0) {
break;
}
}
}
if ($paper_universe === "none" || $paper_universe === "reqpost") {
$prow = $this->astate->placeholder_prow();
$ret = $this->apply_paper($prow, $contacts, $aparser, $req);
if ($ret === 1) {
$any_success = true;
}
}

if (!$any_success) {
$this->astate->mark_matching_errors();
Expand Down
47 changes: 47 additions & 0 deletions test/t_tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,51 @@ function test_tag_anno() {

$this->conf->qe("delete from PaperTagAnno where tag='t'");
}

function test_copy_tag_anno() {
$v = [];
foreach ([0, 10, 10, 10, 30, 31, 32, 50] as $i => $n) {
$v[] = ["t", $i + 1, $n, "H" . ($i + 1)];
}
$this->conf->qe("insert into PaperTagAnno (tag,annoId,tagIndex,heading) values ?v", $v);

$dt = $this->conf->tags()->ensure("t");
$dtt = $this->conf->tags()->ensure("tt");

$dt->invalidate_order_anno();
xassert($dt->has_order_anno());
$dtt->invalidate_order_anno();
xassert(!$dtt->has_order_anno());

xassert_assign($this->u_chair, "action,paper,tag,new_tag\ncopytag,all,t,tt\n");

$dt->invalidate_order_anno();
xassert($dt->has_order_anno());
$dtt->invalidate_order_anno();
xassert($dtt->has_order_anno());

$sv = [[-1, null], [0, 1], [1, 1], [10, 4], [10, 4], [12, 4], [30, 5], [31, 6], [32, 7], [33, 7], [49, 7], [50, 8], [60, 8]];
foreach ($sv as $m) {
xassert_eqq($dt->order_anno_search($m[0])->annoId ?? null, $m[1]);
xassert_eqq($dtt->order_anno_search($m[0])->annoId ?? null, $m[1]);
}

xassert_assign($this->u_chair, "action,paper,tag,new_tag\nmovetag,all,tt,tu\n");

$dtu = $this->conf->tags()->ensure("tu");
$dt->invalidate_order_anno();
xassert($dt->has_order_anno());
$dtt->invalidate_order_anno();
xassert(!$dtt->has_order_anno());
$dtu->invalidate_order_anno();
xassert($dtu->has_order_anno());

$sv = [[-1, null], [0, 1], [1, 1], [10, 4], [10, 4], [12, 4], [30, 5], [31, 6], [32, 7], [33, 7], [49, 7], [50, 8], [60, 8]];
foreach ($sv as $m) {
xassert_eqq($dt->order_anno_search($m[0])->annoId ?? null, $m[1]);
xassert_eqq($dtu->order_anno_search($m[0])->annoId ?? null, $m[1]);
}

$this->conf->qe("delete from PaperTagAnno where tag in ('t','tt','tu')");
}
}

0 comments on commit 72d7d1e

Please sign in to comment.