From 2970c8853827125adc965af3beebb8b1064e0c4f Mon Sep 17 00:00:00 2001 From: Eddie Kohler Date: Fri, 31 Jul 2020 11:46:27 -0400 Subject: [PATCH] Don't report errors for view problems in session state. * "Change view options" reports errors. * Redo unparsing of view options (simpler). Addresses #208. --- manualassign.php | 6 +- reviewprefs.php | 9 +- scripts/script.js | 35 +++- search.php | 12 +- src/api/api_search.php | 4 +- src/api/api_searchconfig.php | 40 ++-- src/api/api_session.php | 12 +- src/conference.php | 15 +- src/listactions/la_get_sub.php | 4 +- src/papercolumn.php | 17 +- src/paperlist.php | 358 ++++++++++++++++++--------------- 11 files changed, 296 insertions(+), 216 deletions(-) diff --git a/manualassign.php b/manualassign.php index 64c5f646d..60b6fc7e3 100644 --- a/manualassign.php +++ b/manualassign.php @@ -202,7 +202,7 @@ function saveAssignments($qreq, $reviewer) { function show_ass_element($pl, $name, $text, $extra = []) { return '
  • ' - . Ht::checkbox("show$name", 1, $pl->showing($name), [ + . Ht::checkbox("show$name", 1, $pl->viewing($name), [ "class" => "uich js-plinfo ignore-diff" . (isset($extra["fold_target"]) ? " js-foldup" : ""), "data-fold-target" => $extra["foldup"] ?? null ]) . "" . Ht::label($text) . '
  • '; @@ -280,13 +280,15 @@ function show_ass_elements($pl) { $search->set_field_highlighter_query(join(" OR ", $hlsearch)); } $pl = new PaperList("reviewAssignment", $search, ["sort" => true], $Qreq); + $pl->apply_view_session(); + $pl->apply_view_qreq(); echo Ht::form($Conf->hoturl_post("manualassign", ["reviewer" => $reviewer->email, "sort" => $Qreq->sort]), ["class" => "assignpc ignore-diff"]), Ht::hidden("t", $Qreq->t), Ht::hidden("q", $Qreq->q); $rev_rounds = $Conf->round_selector_options(false); $expected_round = $Conf->assignment_round_option(false); - echo '
    '; + echo '
    '; if (count($rev_rounds) > 1) { echo '
    ', Ht::select("rev_round", $rev_rounds, $Qreq->rev_round ? : $expected_round, ["id" => "assrevround", "class" => "ignore-diff"]), ' · '; diff --git a/reviewprefs.php b/reviewprefs.php index 4285f4bed..9b2d09eda 100644 --- a/reviewprefs.php +++ b/reviewprefs.php @@ -248,8 +248,9 @@ function parseUploadedPreferences($text, $filename, $apply) { "pageurl" => $Conf->hoturl_site_relative_raw("reviewprefs") ]); $pl = new PaperList("pf", $search, ["sort" => true], $Qreq); -$pl->add_report_default_view(); -$pl->add_session_view(); +$pl->apply_view_report_default(); +$pl->apply_view_session(); +$pl->apply_view_qreq(); $pl->set_table_id_class("foldpl", "pltable-fullw", "p#"); $pl_text = $pl->table_html(["fold_session_prefix" => "pfdisplay.", "footer_extra" => "
    " . Ht::submit("fn", "Save changes", ["data-default-submit-all" => 1, "value" => "saveprefs"]) . "
    ", @@ -259,7 +260,7 @@ function parseUploadedPreferences($text, $filename, $apply) { // DISPLAY OPTIONS echo Ht::form($Conf->hoturl("reviewprefs"), [ "method" => "get", "id" => "searchform", - "class" => "has-fold fold10" . ($pl->showing("authors") ? "o" : "c") + "class" => "has-fold fold10" . ($pl->viewing("authors") ? "o" : "c") ]); if ($Me->privChair) { @@ -290,7 +291,7 @@ function parseUploadedPreferences($text, $filename, $apply) { function show_pref_element($pl, $name, $text, $extra = []) { return '
  • ' - . Ht::checkbox("show$name", 1, $pl->showing($name), [ + . Ht::checkbox("show$name", 1, $pl->viewing($name), [ "class" => "uich js-plinfo ignore-diff" . (isset($extra["fold_target"]) ? " js-foldup" : ""), "data-fold-target" => $extra["fold_target"] ?? null ]) . "" . Ht::label($text) . ''; diff --git a/scripts/script.js b/scripts/script.js index 7b716db81..89f3af25a 100644 --- a/scripts/script.js +++ b/scripts/script.js @@ -3143,11 +3143,11 @@ $(document).on("keypress", "input.js-autosubmit", function (event) { if (event_modkey(event) || event_key(event) !== "Enter") { return; } - var $f = $(event.target).closest("form"), - type = $f.data("autosubmitType"), - defaulte = $f[0] ? $f[0]["default"] : null; + var f = event.target.closest("form"), + type = $(f).data("autosubmitType"), + defaulte = f ? f.elements["default"] : null; if (defaulte && type) { - $f[0].defaultact.value = type; + f.elements.defaultact.value = type; event.target.blur(); defaulte.click(); } @@ -3161,6 +3161,15 @@ handle_ui.on("js-submit-mark", function (event) { $(this).closest("form").data("submitMark", event.target.value); }); +handle_ui.on("js-keydown-enter-submit", function (event) { + if (event.type === "keydown" + && !(event_modkey(event) & (event_modkey.SHIFT | event_modkey.ALT)) + && event_key(event) === "Enter") { + $(event.target.closest("form")).trigger("submit"); + event.preventDefault(); + } +}); + // assignment selection (function ($) { @@ -8513,8 +8522,22 @@ handle_ui.on("js-edit-view-options", function () { $.ajax(hoturl_post("api/viewoptions"), { method: "POST", data: $(this).serialize(), success: function (data) { - if (data.ok) + if (data.ok) { $d.close(); + location.reload(); + } else { + var ta = $d.find("[name=display]")[0]; + while (ta.previousSibling + && hasClass(ta.previousSibling, "feedback")) { + ta.parentElement.removeChild(ta.previousSibling); + } + data.errors = data.errors || ["Error saving view options."]; + for (var i in data.errors) { + $('').insertBefore(ta); + } + addClass(ta, "has-error"); + ta.focus(); + } } }); event.preventDefault(); @@ -8527,7 +8550,7 @@ handle_ui.on("js-edit-view-options", function () { hc.push('
    ' + escape_entities(display_default || "(none)") + '
    '); hc.pop(); hc.push('
    Current view options
    ', '
    '); - hc.push(''); + hc.push(''); hc.pop(); hc.push_actions(['', '']); $d = hc.show(); diff --git a/search.php b/search.php index f845dbb8a..16c05bb9f 100644 --- a/search.php +++ b/search.php @@ -157,11 +157,9 @@ function savesearch() { } assert(!isset($Qreq->display)); $pl = new PaperList("pl", $Search, ["sort" => true], $Qreq); -$pl->add_report_default_view(); -$pl->add_session_view(); -if (isset($Qreq->forceShow)) { - $pl->set_view("force", !!$Qreq->forceShow); -} +$pl->apply_view_report_default(); +$pl->apply_view_session(); +$pl->apply_view_qreq(); if (isset($Qreq->q)) { $pl->set_table_id_class("foldpl", "pltable-fullw", "p#"); if ($SSel->count()) { @@ -200,7 +198,7 @@ function checkbox_item($column, $type, $title, $options = []) { global $pl; $options["class"] = "uich js-plinfo"; $x = ''; $this->item($column, $x); } @@ -457,7 +455,7 @@ function echo_request_as_hidden_inputs($specialscore = false) { // Conflict display if ($Me->privChair) { echo '', - Ht::checkbox("showforce", 1, $pl->showing("force"), + Ht::checkbox("showforce", 1, $pl->viewing("force"), ["id" => "showforce", "class" => "uich js-plinfo"]), " ", Ht::label("Override conflicts", "showforce"), ""; } diff --git a/src/api/api_search.php b/src/api/api_search.php index fbb6cb4ab..406454fb4 100644 --- a/src/api/api_search.php +++ b/src/api/api_search.php @@ -23,8 +23,8 @@ static function search(Contact $user, Qrequest $qreq) { $search = new PaperSearch($user, ["t" => $t, "q" => $q, "qt" => $qreq->qt, "urlbase" => $qreq->urlbase, "reviewer" => $qreq->reviewer]); $pl = new PaperList($qreq->report ? : "pl", $search, ["sort" => true], $qreq); - $pl->add_report_default_view(); - $pl->add_session_view(); + $pl->apply_view_report_default(); + $pl->apply_view_session(); $ih = $pl->ids_and_groups(); return ["ok" => true, "ids" => $ih[0], "groups" => $ih[1], "hotlist" => $pl->session_list_object()->info_string()]; diff --git a/src/api/api_searchconfig.php b/src/api/api_searchconfig.php index fc060abd4..2149a9778 100644 --- a/src/api/api_searchconfig.php +++ b/src/api/api_searchconfig.php @@ -8,36 +8,44 @@ static function viewoptions(Contact $user, Qrequest $qreq) { if ($report !== "pl" && $report !== "pf") { return new JsonResult(400, "Bad request."); } - if ($qreq->method() !== "GET" && $user->privChair) { + $search = new PaperSearch($user, "NONE"); + + if ($qreq->method() === "POST" && $user->privChair) { if (!isset($qreq->display)) { return new JsonResult(400, "Bad request."); } - $base_display = ""; - if ($report === "pl") { - $base_display = $user->conf->review_form()->default_display(); + $pl = new PaperList($report, $search, ["sort" => true]); + $pl->apply_view_report_default(); + $default_view = $pl->unparse_view(true); + $pl->parse_view($qreq->display, PaperList::VIEWORIGIN_EXPLICIT); + $parsed_view = $pl->unparse_view(true); + + // check for errors + $pl->table_html(); + if ($pl->message_set()->has_error()) { + return new JsonResult([ + "ok" => false, + "errors" => $pl->message_set()->error_texts() + ]); } - $display = simplify_whitespace($qreq->display); - if ($display === $base_display) { + + if ($parsed_view === $default_view) { $user->conf->save_setting("{$report}display_default", null); } else { - $user->conf->save_setting("{$report}display_default", 1, $display); + $user->conf->save_setting("{$report}display_default", 1, join(" ", $parsed_view)); } $user->save_session("{$report}display", null); } - $search = new PaperSearch($user, "NONE"); - $pl = new PaperList($report, $search, ["sort" => true]); - $vb = $pl->viewer_list(); - $pl = new PaperList($report, $search, ["sort" => true]); - $pl->add_report_default_view(); - $vd = PaperList::viewer_diff($pl->viewer_list(), $vb); + $pl->apply_view_report_default(); + $vd = $pl->unparse_view(true); $search = new PaperSearch($user, $qreq->q ?? "NONE"); $pl = new PaperList($report, $search, ["sort" => $qreq->sort ?? true]); - $pl->add_report_default_view(); - $pl->add_session_view(); - $vr = PaperList::viewer_diff($pl->viewer_list(), $vb); + $pl->apply_view_report_default(); + $pl->apply_view_session(); + $vr = $pl->unparse_view(true); return new JsonResult([ "ok" => true, "report" => $report, diff --git a/src/api/api_session.php b/src/api/api_session.php index a84db0032..b0c44aeca 100644 --- a/src/api/api_session.php +++ b/src/api/api_session.php @@ -81,18 +81,12 @@ static function setsession(Contact $user, $qreq) { static function change_display(Contact $user, $report, $settings) { $search = new PaperSearch($user, "NONE"); $pl = new PaperList($report, $search, ["sort" => true]); - $pl->add_report_default_view(); - $vd = $pl->viewer_list(); - - $pl = new PaperList($report, $search, ["sort" => true]); - $pl->add_report_default_view(); - $pl->add_session_view(); + $pl->apply_view_report_default(); + $pl->apply_view_session(); foreach ($settings as $k => $v) { $pl->set_view($k, $v); } - $vd = PaperList::viewer_diff($pl->viewer_list(), $vd); - $vd = array_filter($vd, function ($x) { return !str_starts_with($x, "sort:"); }); - + $vd = array_filter($pl->unparse_view(true), function ($x) { return !str_starts_with($x, "sort:"); }); $user->save_session("{$report}display", join(" ", $vd)); } } diff --git a/src/conference.php b/src/conference.php index 40ace62a3..abf3c7f2a 100644 --- a/src/conference.php +++ b/src/conference.php @@ -199,6 +199,8 @@ class Conf { public $xt_context; private $_xt_allow_checkers; public $_xt_allow_callback; + /** @var int */ + private $_xt_checks = 0; /** @var ?array> */ private $_formula_functions; @@ -1185,11 +1187,14 @@ function xt_search_name($map, $name, $user, $found = null, $noalias = false) { if (isset($xt->deprecated) && $xt->deprecated) { error_log("{$this->dbname}: deprecated extension for `{$iname}`\n" . debug_string_backtrace()); } - if (isset($xt->alias) && is_string($xt->alias) && !$noalias) { + if (!isset($xt->alias) || !is_string($xt->alias) || $noalias) { + ++$this->_xt_checks; + if ($this->xt_checkf($xt, $user)) { + return $xt; + } + } else { $name = $xt->alias; break; - } else if ($this->xt_checkf($xt, $user)) { - return $xt; } } } @@ -5002,8 +5007,12 @@ function paper_columns($name, Contact $user) { if ($name === "" || $name[0] === "?") { return []; } + $nchecks = $this->_xt_checks; $uf = $this->xt_search_name($this->paper_column_map(), $name, $user); $ufs = $this->xt_search_factories($this->_paper_column_factories, $name, $user, $uf, "i"); + if (empty($ufs) || $ufs === [null]) { + PaperColumn::column_error($user, $nchecks === $this->_xt_checks ? "No matching field." : "You can’t view that field.", true); + } return array_values(array_filter($ufs, "Conf::xt_resolve_require")); } diff --git a/src/listactions/la_get_sub.php b/src/listactions/la_get_sub.php index eed5fa016..26d506997 100644 --- a/src/listactions/la_get_sub.php +++ b/src/listactions/la_get_sub.php @@ -118,8 +118,8 @@ function run(Contact $user, Qrequest $qreq, SearchSelection $ssel) { $search->restrict_match([$ssel, "is_selected"]); assert(!isset($qreq->display)); $pl = new PaperList("pl", $search, ["sort" => true], $qreq); - $pl->add_report_default_view(); - $pl->add_session_view(); + $pl->apply_view_report_default(); + $pl->apply_view_session(); $pl->set_view("sel", false); list($header, $data) = $pl->text_csv(); return $user->conf->make_csvg("data", CsvGenerator::FLAG_ITEM_COMMENTS) diff --git a/src/papercolumn.php b/src/papercolumn.php index fb8df4251..45a63a48f 100644 --- a/src/papercolumn.php +++ b/src/papercolumn.php @@ -37,9 +37,13 @@ static function make(Conf $conf, $cj, $decorations = []) { } return $pc; } - static function column_error(Contact $user, $msg) { - assert($user->conf->xt_context instanceof PaperList); - $user->conf->xt_context->column_error($msg); + /** @param string $msg + * @param bool $is_default */ + static function column_error(Contact $user, $msg, $is_default = false) { + $c = $user->conf->xt_context; + if ($c instanceof PaperList) { + $c->column_error($msg, $is_default); + } } @@ -408,13 +412,16 @@ function add_decoration($decor) { if ($decor === "full" || $decor === "short") { $this->aufull = $decor === "full"; return $this->__add_decoration($this->aufull ? "full" : null, ["full"]); + } else if ($decor === "anon" || $decor === "noanon") { + $this->anonau = $decor === "anon"; + return $this->__add_decoration($this->anonau ? "anon" : "noanon", ["anon", "noanon"]); } else { return parent::add_user_sort_decoration($decor) || parent::add_decoration($decor); } } function prepare(PaperList $pl, $visible) { - $this->aufull = $this->aufull ?? $pl->showing("aufull"); - $this->anonau = $pl->showing("anonau"); + $this->aufull = $this->aufull ?? $pl->viewing("aufull"); + $this->anonau = $this->anonau ?? $pl->viewing("anonau"); $this->highlight = $pl->search->field_highlighter("authorInformation"); return $pl->user->can_view_some_authors(); } diff --git a/src/paperlist.php b/src/paperlist.php index 207ae4f4a..208996e51 100644 --- a/src/paperlist.php +++ b/src/paperlist.php @@ -149,11 +149,22 @@ class PaperList { private $_paper_linkto; private $_view_kanban = false; private $_view_force = false; - private $_viewing = []; - private $_view_origin = []; + /** @var array */ + private $_viewf = []; + /** @var array> */ private $_view_decorations = []; private $_atab; + const VIEWORIGIN_MASK = 15; + const VIEWORIGIN_NONE = -1; + const VIEWORIGIN_REPORT = 0; + const VIEWORIGIN_DEFAULT_DISPLAY = 1; + const VIEWORIGIN_SESSION = 2; + const VIEWORIGIN_EXPLICIT = 3; + const VIEW_REPORTSHOW = 16; + const VIEW_SHOW = 32; + const VIEW_EDIT = 64; + private $_table_id; private $_table_class; private $_report_id; @@ -239,6 +250,9 @@ function __construct(string $report, PaperSearch $search, $args = [], $qreq = nu $this->_report_id = $report; $this->parse_view($this->_list_columns(), self::VIEWORIGIN_REPORT); + if ($this->viewable_author_types() === 1) { + $this->set_view("anonau", true, self::VIEWORIGIN_REPORT); + } if ($this->sortable) { if (is_string($args["sort"])) { @@ -251,10 +265,10 @@ function __construct(string $report, PaperSearch $search, $args = [], $qreq = nu $qe = $this->search->term(); if ($qe instanceof Then_SearchTerm) { for ($i = 0; $i < $qe->nthen; ++$i) { - $this->set_view_search($qe->child[$i], $i); + $this->apply_view_search($qe->child[$i], $i); } } - $this->set_view_search($qe, -1); + $this->apply_view_search($qe, -1); if ($qreq->forceShow !== null) { $this->set_view("force", !!$qreq->forceShow); @@ -341,11 +355,28 @@ function add_column($name, PaperColumn $col) { "kanban" => -2, "rownum" => -1, "statistics" => -1, "all" => -4, "linkto" => -4 ]; - const VIEWORIGIN_NONE = -1; - const VIEWORIGIN_REPORT = 0; - const VIEWORIGIN_DEFAULT_DISPLAY = 1; - const VIEWORIGIN_SESSION = 2; - const VIEWORIGIN_EXPLICIT = 3; + + /** @param string $fname + * @return bool */ + function viewing($fname) { + $fname = self::$view_synonym[$fname] ?? $fname; + return ($this->_viewf[$fname] ?? 0) >= self::VIEW_SHOW; + } + + /** @param string $fname + * @return bool + * @deprecated */ + function showing($fname) { + return $this->viewing($fname); + } + + /** @param string $k + * @return int */ + function view_origin($k) { + $k = self::$view_synonym[$k] ?? $k; + return ($this->_viewf[$k] ?? 0) & self::VIEWORIGIN_MASK; + } + /** @param string $k * @param 'show'|'hide'|'edit'|bool $v @@ -353,47 +384,56 @@ function add_column($name, PaperColumn $col) { * @param ?list $decorations */ function set_view($k, $v, $origin = null, $decorations = null) { $origin = $origin ?? self::VIEWORIGIN_EXPLICIT; + assert($origin >= self::VIEWORIGIN_REPORT && $origin <= self::VIEWORIGIN_EXPLICIT); if ($v === "show" || $v === "hide") { $v = $v === "show"; } + assert(is_bool($v) || $v === "edit"); + if ($k !== "" && $k[0] === "\"" && $k[strlen($k) - 1] === "\"") { $k = substr($k, 1, -1); } - $k = self::$view_synonym[$k] ?? $k; - - if (($this->_view_origin[$k] ?? -1) > $origin) { - return; - } else if ($k === "all") { - if ($v === false) { - foreach ($this->_viewing as $k => &$v) { - if (!isset(self::$view_fake[$k])) { - $v = false; - } - } + if ($k === "all") { + assert($v === false && $decorations === null); + $views = array_keys($this->_viewf); + foreach ($views as $k) { + $this->set_view($k, $v, $origin, null); } return; } + $k = self::$view_synonym[$k] ?? $k; - $this->_viewing[$k] = $v; - $this->_view_origin[$k] = $origin; - $this->_view_decorations[$k] = empty($decorations) ? null : $decorations; + $flags = &$this->_viewf[$k]; + $flags = $flags ?? 0; + if ($origin === self::VIEWORIGIN_REPORT) { + $flags = ($flags & ~self::VIEW_REPORTSHOW) | ($v ? self::VIEW_REPORTSHOW : 0); + } + if (($flags & self::VIEWORIGIN_MASK) <= $origin) { + $flags = ($flags & self::VIEW_REPORTSHOW) + | $origin + | ($v ? self::VIEW_SHOW : 0) + | ($v === "edit" ? self::VIEW_EDIT : 0); + if (!empty($decorations)) { + $this->_view_decorations[$k] = $decorations; + } else { + unset($this->_view_decorations[$k]); + } - if ($k === "force") { - $this->_view_force = $v; - } else if ($k === "kanban") { - $this->_view_kanban = $v; - } else if ($k === "linkto") { - if (!empty($decorations) - && in_array($decorations[0], ["paper", "paperedit", "assign", "finishreview"])) { - $this->_paper_linkto = $decorations[0]; + if ($k === "force") { + $this->_view_force = $v; + } else if ($k === "kanban") { + $this->_view_kanban = $v; + } else if ($k === "linkto") { + if (!empty($decorations) + && in_array($decorations[0], ["paper", "paperedit", "assign", "finishreview"])) { + $this->_paper_linkto = $decorations[0]; + } + } else if (($k === "aufull" || $k === "anonau") + && $origin === self::VIEWORIGIN_EXPLICIT + && $v + && $this->view_origin("authors") < $origin) { + $this->set_view("authors", true, $origin, null); } - } else if (($k === "aufull" || $k === "anonau") - && $origin === self::VIEWORIGIN_EXPLICIT - && $v === true - && ($this->_view_origin["authors"] ?? -1) < self::VIEWORIGIN_EXPLICIT) { - $this->_viewing["authors"] = true; - $this->_view_origin["authors"] = $origin; - $this->_view_decorations["authors"] = null; } } @@ -428,12 +468,12 @@ private function _add_sorter($name, $decorations, $sort_subset) { } } - /** @param list $words + /** @param list $groups * @param ?int $origin * @param int $sort_subset */ - private function set_view_list($words, $origin, $sort_subset) { + private function set_view_list($groups, $origin, $sort_subset) { $has_sort = false; - foreach (PaperSearch::view_generator($words) as $akd) { + foreach (PaperSearch::view_generator($groups) as $akd) { if ($akd[0] !== "sort" && $sort_subset === -1) { $this->set_view($akd[1], substr($akd[0], 0, 4), $origin, $akd[2]); } @@ -452,7 +492,20 @@ function parse_view($str, $origin = null) { } } - function add_report_default_view() { + /** @param int $sort_subset */ + private function apply_view_search(SearchTerm $qe, $sort_subset) { + $nsort = count($this->_sortcol); + $this->set_view_list($qe->get_float("view") ?? [], null, $sort_subset); + if ($nsort === count($this->_sortcol) + && ($sortcol = $qe->default_sort_column(true, $this->search)) + && $sortcol->prepare($this, PaperColumn::PREP_SORT)) { + assert(!!$sortcol->sort); + $sortcol->sort_subset = $sort_subset; + $this->_sortcol[] = $sortcol; + } + } + + function apply_view_report_default() { if ($this->_report_id === "pl") { $s = $this->conf->setting_data("pldisplay_default") ?? $this->conf->review_form()->default_display(); @@ -464,31 +517,75 @@ function add_report_default_view() { $this->parse_view($s, self::VIEWORIGIN_DEFAULT_DISPLAY); } - function add_session_view() { + function apply_view_session() { if ($this->_report_id === "pl" || $this->_report_id === "pf") { $s = $this->user->session("{$this->_report_id}display"); $this->parse_view($s, self::VIEWORIGIN_SESSION); } } - /** @param int $sort_subset */ - private function set_view_search(SearchTerm $qe, $sort_subset) { - $nsort = count($this->_sortcol); - $this->set_view_list($qe->get_float("view") ?? [], null, $sort_subset); - if ($nsort === count($this->_sortcol) - && ($sortcol = $qe->default_sort_column(true, $this->search)) - && $sortcol->prepare($this, PaperColumn::PREP_SORT)) { - assert(!!$sortcol->sort); - $sortcol->sort_subset = $sort_subset; - $this->_sortcol[] = $sortcol; + function apply_view_qreq() { + foreach ($this->qreq as $k => $v) { + if (str_starts_with($k, "show") && $v) { + $name = substr($k, 4); + $this->set_view($name, true, self::VIEWORIGIN_SESSION, $this->_view_decorations[$name] ?? null); + } else if ($k === "forceShow") { + $this->set_view("force", !!$v, self::VIEWORIGIN_SESSION); + } } } - /** @param string $k - * @return int */ - function view_origin($k) { - $k = self::$view_synonym[$k] ?? $k; - return $this->_view_origin[$k] ?? self::VIEWORIGIN_NONE; + /** @param bool $report_diff + * @return list */ + function unparse_view($report_diff = false) { + $this->_prepare(); + $res = []; + $nextpos = 1000000; + foreach ($this->_viewf as $k => $v) { + if ($report_diff + ? ($v >= self::VIEW_SHOW) !== (($v & self::VIEW_REPORTSHOW) !== 0) + : $v >= self::VIEW_SHOW) { + $name = $k; + $pos = self::$view_fake[$k] ?? null; + if ($pos === null) { + list($name, $decorations) = self::parse_column($k); + $fs = $this->conf->paper_columns($name, $this->user); + if (count($fs) && isset($fs[0]->position)) { + $pos = $fs[0]->position; + $name = $fs[0]->name; + } else { + $pos = $nextpos++; + } + } + $key = "$pos $name"; + if ($v >= self::VIEW_EDIT) { + $kw = "edit"; + } else if ($v >= self::VIEW_SHOW) { + $kw = "show"; + } else { + $kw = "hide"; + } + $res[$key] = PaperSearch::unparse_view($kw, $name, $this->_view_decorations[$k] ?? null); + } + } + if (((($this->_viewf["anonau"] ?? 0) >= self::VIEW_SHOW && $this->conf->submission_blindness() == Conf::BLIND_OPTIONAL) + || ($this->_viewf["aufull"] ?? 0) >= self::VIEW_SHOW) + && ($this->_viewf["authors"] ?? 0) < self::VIEW_SHOW) { + $res["150 authors"] = "hide:authors"; + } + ksort($res, SORT_NATURAL); + $res = array_values($res); + + foreach ($this->sorters() as $s) { + $res[] = PaperSearch::unparse_view("sort", $s->name, $s->decorations()); + if ($s->name === "id") { + break; + } + } + while (!empty($res) && $res[count($res) - 1] === "sort:id") { + array_pop($res); + } + return $res; } @@ -616,10 +713,10 @@ private function _set_sort_etag_anno_groups() { } $dt = $this->conf->tags()->add(Tagger::base($etag)); if (!$dt->has_order_anno() - && ($this->_viewing["#$etag"] ?? false) !== "edit" - && ($this->_viewing["#$alt_etag"] ?? false) !== "edit" - && ($this->_viewing["tagval:$etag"] ?? false) !== "edit" - && ($this->_viewing["tagval:$alt_etag"] ?? false) !== "edit") { + && !(($this->_viewf["#$etag"] ?? 0) & self::VIEW_EDIT) + && !(($this->_viewf["#$alt_etag"] ?? 0) & self::VIEW_EDIT) + && !(($this->_viewf["tagval:$etag"] ?? 0) & self::VIEW_EDIT) + && !(($this->_viewf["tagval:$alt_etag"] ?? 0) & self::VIEW_EDIT)) { return; } $srch = $this->search; @@ -800,33 +897,32 @@ private function _compute_has($key) { } - function column_error($text) { - if ($this->_current_find_column) { - $this->_column_errors_by_name[$this->_current_find_column][] = $text; + function column_error($text, $is_default = false) { + if (($name = $this->_current_find_column) + && (!$is_default || empty($this->_column_errors_by_name[$name]))) { + $this->_column_errors_by_name[$name][] = $text; } } - /** @param string $name - * @return list */ - private function find_columns($name) { - $viewdecorations = null; - if (str_starts_with($name, "[")) { - $words = SearchSplitter::split_balanced_parens(substr($name, 1, strlen($name) - (str_ends_with($name, "]") ? 2 : 1))); - $name = $words[0] ?? "NONE"; - $viewdecorations = array_slice($words, 1); + /** @param string $str + * @return array{string,?list} */ + static private function parse_column($str) { + if (str_starts_with($str, "[")) { + $ws = SearchSplitter::split_balanced_parens(substr($str, 1, strlen($str) - (str_ends_with($str, "]") ? 2 : 1))); + return [$ws[0] ?? "?", count($ws) > 1 ? array_slice($ws, 1) : null]; + } else { + return [$str, null]; } + } + + /** @param string $str + * @return list */ + private function find_columns($str) { + list($name, $viewdecorations) = self::parse_column($str); if (!array_key_exists($name, $this->_columns_by_name)) { $this->_current_find_column = $name; - $fs = $this->conf->paper_columns($name, $this->user); - if (!$fs && !isset($this->_column_errors_by_name[$name])) { - if ($this->conf->paper_columns($name, $this->conf->root_user())) { - $this->_column_errors_by_name[$name][] = "Permission error."; - } else { - $this->_column_errors_by_name[$name][] = "No such column."; - } - } $nfs = []; - foreach ($fs as $fdef) { + foreach ($this->conf->paper_columns($name, $this->user) as $fdef) { $decorations = $viewdecorations ?? $this->_view_decorations[$fdef->name] ?? $this->_view_decorations[$name] @@ -851,12 +947,13 @@ private function find_column($name) { return ($this->find_columns($name))[0] ?? null; } - private function _expand_view_column($k, $report) { - if (!isset(self::$view_fake[$k]) && ($this->_viewing[$k] ?? false)) { + private function _expand_view_column($k) { + if (!isset(self::$view_fake[$k]) + && ($this->_viewf[$k] ?? 0) >= self::VIEW_SHOW) { $fs = $this->find_columns($k); - if (!$fs && $report && isset($this->_column_errors_by_name[$k])) { - foreach ($this->_column_errors_by_name[$k] as $i => $err) { - $this->message_set()->error_at($k, htmlspecialchars($k) . ": " . $err); + if (!$fs && $this->view_origin($k) >= self::VIEWORIGIN_EXPLICIT) { + foreach ($this->_column_errors_by_name[$k] ?? [] as $err) { + $this->message_set()->error_at($k, "Can’t show " . htmlspecialchars($k) . ": " . $err); } } return $fs; @@ -871,23 +968,23 @@ private function _columns() { $this->table_attr = []; assert(empty($this->row_attr)); - // extract columns from _viewing + // extract columns from _viewf $old_context = $this->conf->xt_swap_context($this); - $fields = $editable = []; - foreach ($this->_viewing as $k => $v) { - foreach ($this->_expand_view_column($k, true) as $f) { - assert($v === true || $v === "edit"); + $fields = $viewf = []; + foreach ($this->_viewf as $k => $v) { + foreach ($this->_expand_view_column($k) as $f) { + assert($v >= self::VIEW_SHOW); $fields[$f->name] = $fields[$f->name] ?? $f; - $editable[$f->name] = ($editable[$f->name] ?? false) || $v === "edit"; + $viewf[$f->name] = $this->_viewf[$f->name] ?? $v; } } $this->conf->xt_swap_context($old_context); - // update _viewing, prepare, mark fields editable + // update _viewf, prepare, mark fields editable $fields2 = []; foreach ($fields as $k => $f) { - $this->_viewing[$k] = $editable[$f->name] ? "edit" : true; - if ($editable[$k]) { + $this->_viewf[$k] = $viewf[$k]; + if ($viewf[$k] >= self::VIEW_EDIT) { $f->mark_editable(); } $f->is_visible = true; @@ -1003,15 +1100,6 @@ function viewable_author_types() { } } - function showing($fname) { - $fname = self::$view_synonym[$fname] ?? $fname; - if (isset($this->qreq["show$fname"])) { - return true; - } else { - return $this->_viewing[$fname] ?? false; - } - } - private function _wrap_conflict($main_content, $override_content, PaperColumn $fdef) { if ($main_content === $override_content) { return $main_content; @@ -1313,15 +1401,15 @@ private function _analyze_folds($rstate, $fieldDef) { } } // authorship requires special handling - $classes[] = "fold2" . ($this->showing("anonau") ? "o" : "c"); - $classes[] = "fold4" . ($this->showing("aufull") ? "o" : "c"); + $classes[] = "fold2" . ($this->viewing("anonau") ? "o" : "c"); + $classes[] = "fold4" . ($this->viewing("aufull") ? "o" : "c"); if ($this->user->is_track_manager()) { - $classes[] = "fold5" . ($this->showing("force") ? "o" : "c"); + $classes[] = "fold5" . ($this->viewing("force") ? "o" : "c"); } if ($has_sel) { - $classes[] = "fold6" . ($this->showing("rownum") ? "o" : "c"); + $classes[] = "fold6" . ($this->viewing("rownum") ? "o" : "c"); } - $classes[] = "fold7" . ($this->showing("statistics") ? "o" : "c"); + $classes[] = "fold7" . ($this->viewing("statistics") ? "o" : "c"); $classes[] = "fold8" . ($has_statistics ? "o" : "c"); $this->table_attr["data-columns"] = $jscol; } @@ -1990,54 +2078,4 @@ function text_csv($options = []) { return [$header, $body]; } - - - function viewer_list() { - $this->_prepare(); - $res = []; - foreach ($this->_viewing as $k => $v) { - if (!$v) { - // skip - } else if (isset(self::$view_fake[$k])) { - $key = self::$view_fake[$k] . " " . $k; - $res[$key] = "show:$k"; - } else { - foreach ($this->_expand_view_column($k, false) as $col) { - $key = ($col->position ? : 0) . " " . $col->name; - $res[$key] = PaperSearch::unparse_view($v, $col->name, $col->decorations()); - } - } - } - if (((($this->_viewing["anonau"] ?? false) && $this->conf->submission_blindness() == Conf::BLIND_OPTIONAL) - || ($this->_viewing["aufull"] ?? false)) - && !($this->_viewing["authors"] ?? false)) { - $res["150 authors"] = "hide:authors"; - } - ksort($res, SORT_NATURAL); - $res = array_values($res); - - foreach ($this->sorters() as $s) { - $res[] = PaperSearch::unparse_view("sort", $s->name, $s->decorations()); - if ($s->name === "id") { - break; - } - } - while (!empty($res) && $res[count($res) - 1] === "sort:id") { - array_pop($res); - } - return $res; - } - - static function viewer_diff($v1, $v2) { - $res = []; - foreach ($v1 as $x) { - if (!str_starts_with($x, "show:") || !in_array($x, $v2)) - $res[] = $x; - } - foreach ($v2 as $x) { - if (str_starts_with($x, "show:") && !in_array($x, $v1)) - $res[] = "hide:" . substr($x, 5); - } - return $res; - } }