diff --git a/src/formula.php b/src/formula.php index 0bcdd04a80..24a37386de 100644 --- a/src/formula.php +++ b/src/formula.php @@ -155,34 +155,34 @@ function format() { } /** @return bool */ - function has_format() { + final function has_format() { return $this->_format !== Fexpr::FUNKNOWN; } /** @return bool */ - function math_format() { + final function math_format() { return $this->_format !== Fexpr::FREVIEWER && $this->_format !== Fexpr::FSUBFIELD; } /** @return bool */ - function nonnullable_format() { + final function nonnullable_format() { return $this->_format === Fexpr::FNUMERIC || $this->_format === Fexpr::FBOOL; } /** @return bool */ - function nonnull_format() { + final function nonnull_format() { return $this->nonnullable_format() && $this->_format_detail; } /** @return mixed */ - function format_detail() { + final function format_detail() { return $this->_format_detail; } /** @return string */ - function format_description() { + final function format_description() { if ($this->_format === Fexpr::FREVIEWFIELD) { return $this->_format_detail->name; } else if ($this->_format === Fexpr::FSUBFIELD) { @@ -194,7 +194,7 @@ function format_description() { /** @param int $format * @param mixed $format_detail */ - function set_format($format, $format_detail = null) { + final function set_format($format, $format_detail = null) { assert($this->_format === Fexpr::FUNKNOWN); $this->_format = $format; $this->_format_detail = $format_detail; @@ -348,6 +348,13 @@ function compiled_relation($cmp, $other_expr = null) { return $cmp; } + /** @param array &$oids */ + function paper_options(&$oids) { + foreach ($this->args as $a) { + $a->paper_options($oids); + } + } + #[\ReturnTypeWillChange] function jsonSerialize() { if ($this->op) { @@ -2871,6 +2878,13 @@ function add_query_options(&$queryOptions) { } } + /** @var array &$ods */ + function paper_options(&$oids) { + if ($this->check()) { + $this->_parse->fexpr->paper_options($oids); + } + } + /** @return bool */ function viewable_by(Contact $user) { return $this->check($this->user ?? $user) diff --git a/src/formulas/f_optionpresent.php b/src/formulas/f_optionpresent.php index 98c85794bc..b9e2e5a0c1 100644 --- a/src/formulas/f_optionpresent.php +++ b/src/formulas/f_optionpresent.php @@ -1,6 +1,6 @@ option = $option; $this->set_format(Fexpr::FBOOL); } + function paper_options(&$oids) { + $oids[$this->option->id] = true; + } function viewable_by(Contact $user) { return $user->can_view_some_option($this->option); } diff --git a/src/formulas/f_optionvalue.php b/src/formulas/f_optionvalue.php index 66fb418f21..a2b852ef8f 100644 --- a/src/formulas/f_optionvalue.php +++ b/src/formulas/f_optionvalue.php @@ -1,6 +1,6 @@ option = $option; } + function paper_options(&$oids) { + $oids[$this->option->id] = true; + } function viewable_by(Contact $user) { return $user->can_view_some_option($this->option); } diff --git a/src/formulas/f_realnumberoption.php b/src/formulas/f_realnumberoption.php index 8d50b377a6..d148519d7c 100644 --- a/src/formulas/f_realnumberoption.php +++ b/src/formulas/f_realnumberoption.php @@ -1,6 +1,6 @@ option = $option; } + function paper_options(&$oids) { + $oids[$this->option->id] = true; + } function viewable_by(Contact $user) { return $user->can_view_some_option($this->option); } diff --git a/src/formulas/f_topic.php b/src/formulas/f_topic.php index 4f81657581..70db3104f7 100644 --- a/src/formulas/f_topic.php +++ b/src/formulas/f_topic.php @@ -29,6 +29,9 @@ static function parse_modifier(FormulaCall $ff, $arg, $rest, Formula $formula) { return false; } } + function paper_options(&$oids) { + $oids[PaperOption::TOPICSID] = true; + } function compile(FormulaCompiler $state) { $state->queryOptions["topics"] = true; $texpr = $state->_prow() . "->topic_list()"; diff --git a/src/formulas/f_topicscore.php b/src/formulas/f_topicscore.php index 832d9ab94e..19a32f98a5 100644 --- a/src/formulas/f_topicscore.php +++ b/src/formulas/f_topicscore.php @@ -1,6 +1,6 @@ isPC; } diff --git a/src/paperoption.php b/src/paperoption.php index c398007010..4e462f7d3f 100644 --- a/src/paperoption.php +++ b/src/paperoption.php @@ -531,13 +531,18 @@ final function override_exists_condition($x) { } } /** @return SearchTerm */ - private function exists_term() { - if ($this->_exists_state === 0 && $this->_exists_term === null) { + final function exists_term() { + if ($this->_exists_term !== null) { + return $this->_exists_term; + } + if ($this->_exists_state === 0) { $s = new PaperSearch($this->conf->root_user(), $this->exists_if); $s->set_expand_automatic(true); $this->_exists_term = $s->full_term(); /** @phan-suppress-next-line PhanAccessReadOnlyProperty */ $this->_phase = Phase_SearchTerm::term_phase($this->_exists_term); + } else { + $this->_exists_term = new True_SearchTerm; } return $this->_exists_term; } @@ -552,7 +557,9 @@ final function has_complex_exists_condition() { /** @return bool */ final function is_final() { // compute phase of complex exists condition - $this->exists_term(); + if ($this->_exists_state === 0 && $this->_exists_term === null) { + $this->exists_term(); + } return $this->_phase === PaperInfo::PHASE_FINAL; } /** @param bool $use_script_expression @@ -757,7 +764,9 @@ function jsonSerialize() { if ($this->configurable !== true) { $j->configurable = $this->configurable; } - $this->exists_term(); // to set `_phase` + if ($this->_exists_state === 0 && $this->_exists_term === null) { + $this->exists_term(); // to set `_phase` + } if ($this->_phase === PaperInfo::PHASE_FINAL) { $j->final = true; } diff --git a/src/search/st_author.php b/src/search/st_author.php index 020b0da196..76be34dd33 100644 --- a/src/search/st_author.php +++ b/src/search/st_author.php @@ -59,6 +59,7 @@ function is_sqlexpr_precise() { && !$this->csm->test(0); } function test(PaperInfo $row, $xinfo) { + // XXX presence condition $n = 0; $can_view = $this->user->allow_view_authors($row); if ($this->csm->has_contacts()) { diff --git a/src/search/st_authormatch.php b/src/search/st_authormatch.php index ceebfacd88..0edd65d117 100644 --- a/src/search/st_authormatch.php +++ b/src/search/st_authormatch.php @@ -48,6 +48,7 @@ function sqlexpr(SearchQueryInfo $sqi) { } } function test(PaperInfo $row, $xinfo) { + // XXX presence condition if (!$this->user->allow_view_authors($row)) { return false; } diff --git a/src/search/st_formula.php b/src/search/st_formula.php index d153c10b19..46f82bda18 100644 --- a/src/search/st_formula.php +++ b/src/search/st_formula.php @@ -52,6 +52,9 @@ static function parse_graph($word, SearchWord $sword, PaperSearch $srch) { } return null; } + function paper_options(&$oids) { + $this->formula->paper_options($oids); + } function sqlexpr(SearchQueryInfo $sqi) { $this->formula->add_query_options($sqi->query_options); return "true"; diff --git a/src/search/st_option.php b/src/search/st_option.php index 4b8870928e..e764c17b75 100644 --- a/src/search/st_option.php +++ b/src/search/st_option.php @@ -28,6 +28,10 @@ function paper_requirements(&$options) { } } + function paper_options(&$oids) { + $oids[$this->option->id] = true; + } + function sqlexpr(SearchQueryInfo $sqi) { if ($this->option->id > 0) { $sqi->add_options_columns(); diff --git a/src/search/st_pdf.php b/src/search/st_pdf.php index 3ba9a8c5eb..54f6cf91dd 100644 --- a/src/search/st_pdf.php +++ b/src/search/st_pdf.php @@ -95,6 +95,7 @@ function is_sqlexpr_precise() { return $this->dtype === DTYPE_SUBMISSION && $this->format_problem === null; } function test(PaperInfo $row, $xinfo) { + // XXX presence condition $dtype = $this->dtype; if ($dtype === null) { if ($row->finalPaperStorageId > 1 diff --git a/src/searchterm.php b/src/searchterm.php index 51431bb87d..f46e106502 100644 --- a/src/searchterm.php +++ b/src/searchterm.php @@ -61,20 +61,20 @@ function negate() { /** @param bool $negate * @return SearchTerm */ - function negate_if($negate) { + final function negate_if($negate) { return $negate ? $this->negate() : $this; } /** @param string $command * @param SearchWord $sword * @return $this */ - function add_view_anno($command, $sword) { + final function add_view_anno($command, $sword) { $this->float["view"][] = new SearchViewCommand($command, $sword); return $this; } /** @return list */ - function view_commands() { + final function view_commands() { $v = $this->float["view"] ?? []; if (!empty($v)) { $v = SearchViewCommand::analyze($v); @@ -84,7 +84,7 @@ function view_commands() { /** @param string $field * @return ?SearchViewCommand */ - function find_view_command($field) { + final function find_view_command($field) { foreach ($this->view_commands() as $svc) { if ($svc->keyword === $field) return $svc; @@ -140,7 +140,7 @@ protected function assign_context($term, $clone_of = null) { /** @param ?SearchStringContext $context * @return ?array{int,int} */ - function strspan_in($context) { + final function strspan_in($context) { if ($this->pos1 === null) { return null; } @@ -184,6 +184,10 @@ function simple_search(&$options) { function paper_requirements(&$options) { } + /** @param array &$oids */ + function paper_options(&$oids) { + } + /** @return string */ function sqlexpr(SearchQueryInfo $sqi) { @@ -461,6 +465,11 @@ function paper_requirements(&$options) { $ch->paper_requirements($options); } } + function paper_options(&$oids) { + foreach ($this->child as $ch) { + $ch->paper_options($oids); + } + } function is_sqlexpr_precise() { foreach ($this->child as $ch) { if (!$ch->is_sqlexpr_precise()) @@ -848,6 +857,7 @@ protected function _finish() { } return $this; } + function visit($visitor) { // Only visit non-highlight terms $x = []; @@ -857,6 +867,12 @@ function visit($visitor) { return $visitor($this, ...$x); } + function paper_options(&$oids) { + for ($i = 0; $i !== $this->nthen; ++$i) { + $this->child[$i]->paper_options($oids); + } + } + /** @return int */ function ngroups() { return $this->_group_offsets[$this->nthen]; @@ -1489,6 +1505,7 @@ function is_sqlexpr_precise() { return $this->trivial && !$this->authorish; } function test(PaperInfo $row, $xinfo) { + // XXX presence conditions $data = $row->{$this->field}(); if ($this->authorish && !$this->user->allow_view_authors($row)) { $data = ""; diff --git a/src/settings/s_subfieldcondition.php b/src/settings/s_subfieldcondition.php index 86eeb90017..6e986c1a89 100644 --- a/src/settings/s_subfieldcondition.php +++ b/src/settings/s_subfieldcondition.php @@ -46,19 +46,37 @@ static private function validate1($sv, $ctr, $type, $field, $status, $prow) { } $siname = "sf/{$ctr}/" . ($type === "exists_if" ? "condition" : "edit_condition"); + // save recursion state + $scr = $sv->conf->setting("__sf_condition_recursion"); + $scrd = $sv->conf->setting_data("__sf_condition_recursion"); + $sv->conf->change_setting("__sf_condition_recursion", -1); + + // make search $ps = new PaperSearch($sv->conf->root_user(), $q); foreach ($ps->message_list() as $mi) { $sv->append_item_at($siname, $mi); } - $scr = $sv->conf->setting("__sf_condition_recursion"); - $scrd = $sv->conf->setting_data("__sf_condition_recursion"); - $sv->conf->change_setting("__sf_condition_recursion", -1); + // check script expression if ($ps->main_term()->script_expression($prow, SearchTerm::ABOUT_PAPER) === null) { $sv->msg_at($siname, "<0>Invalid search in field condition", $status); $sv->inform_at($siname, "<0>Field conditions are limited to simple search keywords."); } + + // check recursion (catches more cases than script expression alone) + $pos = 0; + $oids = []; + $ps->main_term()->paper_options($oids); + while (count($oids) > $pos) { + $check = array_keys($oids); + while ($pos !== count($check)) { + $sv->conf->option_by_id($check[$pos])->exists_term()->paper_options($oids); + ++$pos; + } + } + if ($sv->conf->setting("__sf_condition_recursion") > 0 + || isset($oids[$field->id]) || ($status === 1 && $scr === $field->id && $scrd === $type)) { $sv->msg_at($siname, "<0>Self-referential search in field condition", 2); } diff --git a/src/sitype.php b/src/sitype.php index 5b0892eee4..a5f867aaad 100644 --- a/src/sitype.php +++ b/src/sitype.php @@ -143,7 +143,7 @@ function json_examples(Si $si, SettingValues $sv) { class Radio_Sitype extends Sitype { function parse_reqv($vstr, Si $si, SettingValues $sv) { - $values = $si->values($sv); + $values = $si->values($sv) ?? []; foreach ($values as $allowedv) { if ((string) $allowedv === $vstr) return $allowedv; diff --git a/test/t_settings.php b/test/t_settings.php index d972396bd8..678fe20b29 100644 --- a/test/t_settings.php +++ b/test/t_settings.php @@ -1864,7 +1864,7 @@ function test_ioptions_title() { function test_sf_condition_recursion_live() { $old_options = $this->conf->setting_data("options"); - $j = json_decode($this->conf->setting_data("options")); + $j = json_decode($this->conf->setting_data("options")) ?? []; xassert_eqq($j[3]->id, 1); xassert_eqq($j[3]->name, "Brownies"); xassert_eqq($j[3]->exists_if ?? null, null);