diff --git a/reportbuilder/classes/local/filters/tags.php b/reportbuilder/classes/local/filters/tags.php index b670b431c7a..f2239cb657b 100644 --- a/reportbuilder/classes/local/filters/tags.php +++ b/reportbuilder/classes/local/filters/tags.php @@ -45,6 +45,12 @@ class tags extends base { /** @var int Filter for selected tags */ public const EQUAL_TO = 2; + /** @var int Tags are not present */ + public const EMPTY = 3; + + /** @var int Filter for excluded tags */ + public const NOT_EQUAL_TO = 4; + /** * Returns an array of comparison operators * @@ -54,7 +60,9 @@ class tags extends base { $operators = [ self::ANY_VALUE => new lang_string('filterisanyvalue', 'core_reportbuilder'), self::NOT_EMPTY => new lang_string('filterisnotempty', 'core_reportbuilder'), + self::EMPTY => new lang_string('filterisempty', 'core_reportbuilder'), self::EQUAL_TO => new lang_string('filterisequalto', 'core_reportbuilder'), + self::NOT_EQUAL_TO => new lang_string('filterisnotequalto', 'core_reportbuilder'), ]; return $this->filter->restrict_limited_operators($operators); @@ -84,7 +92,7 @@ class tags extends base { $valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()); $mform->addElement('autocomplete', "{$this->name}_value", $valuelabel, $tags, ['multiple' => true]) ->setHiddenLabel(true); - $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'neq', self::EQUAL_TO); + $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'in', [self::ANY_VALUE, self::EMPTY, self::NOT_EMPTY]); } /** @@ -104,12 +112,21 @@ class tags extends base { if ($operator === self::NOT_EMPTY) { $select = "{$fieldsql} IS NOT NULL"; + } else if ($operator === self::EMPTY) { + $select = "{$fieldsql} IS NULL"; } else if ($operator === self::EQUAL_TO && !empty($tags)) { [$tagselect, $tagselectparams] = $DB->get_in_or_equal($tags, SQL_PARAMS_NAMED, database::generate_param_name() . '_'); $select = "{$fieldsql} {$tagselect}"; $params = array_merge($params, $tagselectparams); + } else if ($operator === self::NOT_EQUAL_TO && !empty($tags)) { + [$tagselect, $tagselectparams] = $DB->get_in_or_equal($tags, SQL_PARAMS_NAMED, + database::generate_param_name() . '_', false); + + // We should also return those elements that aren't tagged at all. + $select = "COALESCE({$fieldsql}, 0) {$tagselect}"; + $params = array_merge($params, $tagselectparams); } else { // Invalid/inactive (any value) filter.. return ['', []]; diff --git a/reportbuilder/tests/local/filters/tags_test.php b/reportbuilder/tests/local/filters/tags_test.php index 8e80a02f3c8..a3148673c08 100644 --- a/reportbuilder/tests/local/filters/tags_test.php +++ b/reportbuilder/tests/local/filters/tags_test.php @@ -42,8 +42,11 @@ class tags_test extends advanced_testcase { return [ 'Any value' => [tags::ANY_VALUE, null, ['course01', 'course01', 'course02', 'course03']], 'Not empty' => [tags::NOT_EMPTY, null, ['course01', 'course01', 'course02']], + 'Empty' => [tags::EMPTY, null, ['course03']], 'Equal to unselected' => [tags::EQUAL_TO, null, ['course01', 'course01', 'course02', 'course03']], 'Equal to selected tag' => [tags::EQUAL_TO, 'cat', ['course01']], + 'Not equal to unselected' => [tags::NOT_EQUAL_TO, null, ['course01', 'course01', 'course02', 'course03']], + 'Not equal to selected tag' => [tags::NOT_EQUAL_TO, 'fish', ['course01', 'course01', 'course03']], ]; } diff --git a/tag/tests/reportbuilder/datasource/tags_test.php b/tag/tests/reportbuilder/datasource/tags_test.php index 0a19b3046b6..5aebcb762bb 100644 --- a/tag/tests/reportbuilder/datasource/tags_test.php +++ b/tag/tests/reportbuilder/datasource/tags_test.php @@ -166,10 +166,17 @@ class tags_test extends core_reportbuilder_testcase { ], false], // Tag. - 'Filter tag name' => ['tag:name', [ + 'Filter tag name equal to' => ['tag:name', [ 'tag:name_operator' => tags_filter::EQUAL_TO, 'tag:name_value' => [-1], ], false], + 'Filter tag name not equal to' => ['tag:name', [ + 'tag:name_operator' => tags_filter::NOT_EQUAL_TO, + 'tag:name_value' => [-1], + ], true], + 'Filter tag name empty' => ['tag:name', [ + 'tag:name_operator' => tags_filter::EMPTY, + ], false], 'Filter tag name not empty' => ['tag:name', [ 'tag:name_operator' => tags_filter::NOT_EMPTY, ], true],