Skip to content

Commit

Permalink
MDL-78879 reportbuilder: allow for negation of category filter.
Browse files Browse the repository at this point in the history
Add "Equal to" and "Not equal to" operators to the filter class.

AMOS BEGIN
 CPY [subcats,qtype_randomsamatch],[includesubcategories,moodle]
AMOS END
  • Loading branch information
paulholden committed Aug 2, 2023
1 parent a1d5d1b commit 501a170
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 9 deletions.
6 changes: 6 additions & 0 deletions course/tests/reportbuilder/datasource/courses_test.php
Expand Up @@ -22,6 +22,7 @@
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\category;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\tags;
Expand Down Expand Up @@ -219,6 +220,11 @@ public function datasource_filters_provider(): array {
return [
// Category.
'Filter category' => ['course_category:name', [
'course_category:name_operator' => category::NOT_EQUAL_TO,
'course_category:name_value' => -1,
], true],
'Filter category (no match)' => ['course_category:name', [
'course_category:name_operator' => category::EQUAL_TO,
'course_category:name_value' => -1,
], false],
'Filter category name' => ['course_category:text', [
Expand Down
1 change: 1 addition & 0 deletions lang/en/moodle.php
Expand Up @@ -1118,6 +1118,7 @@
$string['includenoneusers'] = 'Include no users';
$string['includeroleassignments'] = 'Include role assignments';
$string['includesitefiles'] = 'Include site files used in this course';
$string['includesubcategories'] = 'Include subcategories';
$string['includeuserfiles'] = 'Include user files';
$string['increasesections'] = 'Increase the number of sections';
$string['indicator:accessesafterend'] = 'Course accessed after end date';
Expand Down
36 changes: 33 additions & 3 deletions reportbuilder/classes/local/filters/category.php
Expand Up @@ -19,6 +19,7 @@
namespace core_reportbuilder\local\filters;

use core_course_category;
use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;

Expand All @@ -36,20 +37,43 @@
*/
class category extends base {

/** @var int Category is equal to */
public const EQUAL_TO = 0;

/** @var int Category is not equal to */
public const NOT_EQUAL_TO = 1;

/**
* Returns an array of comparison operators
*
* @return array
*/
private function get_operators(): array {
$operators = [
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);
}

/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$label = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())
->setHiddenLabel(true);

// See MDL-74627: in order to set the default value to "No selection" we need to prepend an empty value.
$requiredcapabilities = $this->filter->get_options()['requiredcapabilities'] ?? '';
$categories = [0 => ''] + core_course_category::make_categories_list($requiredcapabilities);

$mform->addElement('autocomplete', "{$this->name}_value", $label, $categories)->setHiddenLabel(true);
$mform->addElement('advcheckbox', "{$this->name}_subcategories", get_string('viewallsubcategories'));
$valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$mform->addElement('autocomplete', "{$this->name}_value", $valuelabel, $categories)->setHiddenLabel(true);
$mform->addElement('advcheckbox', "{$this->name}_subcategories", get_string('includesubcategories'));
}

/**
Expand All @@ -63,6 +87,7 @@ public function get_sql_filter(array $values): array {

[$fieldsql, $params] = $this->filter->get_field_sql_and_params();

$operator = (int) ($values["{$this->name}_operator"] ?? self::EQUAL_TO);
$category = (int) ($values["{$this->name}_value"] ?? 0);
$subcategories = !empty($values["{$this->name}_subcategories"]);

Expand Down Expand Up @@ -92,6 +117,11 @@ public function get_sql_filter(array $values): array {
)";
}

// If specified "Not equal to", then negate the entire clause.
if ($operator === self::NOT_EQUAL_TO) {
$sql = "NOT ({$sql})";
}

return [$sql, $params];
}

Expand Down
35 changes: 29 additions & 6 deletions reportbuilder/tests/local/filters/category_test.php
Expand Up @@ -40,32 +40,54 @@ class category_test extends advanced_testcase {
*/
public function get_sql_filter_provider(): array {
return [
['One', false, ['One']],
['One', true, ['One', 'Two', 'Three']],
['Two', true, ['Two', 'Three']],
['Three', true, ['Three']],
[null, false, ['Category 1', 'One', 'Two', 'Three']],
// Equal to.
['One', category::EQUAL_TO, false, ['One']],
['One', category::EQUAL_TO, true, ['One', 'Two', 'Three']],
['Two', category::EQUAL_TO, true, ['Two', 'Three']],
['Three', category::EQUAL_TO, true, ['Three']],

// Not equal to.
['One', category::NOT_EQUAL_TO, false, ['Category 1', 'Two', 'Three', 'Four', 'Five', 'Six']],
['One', category::NOT_EQUAL_TO, true, ['Category 1', 'Four', 'Five', 'Six']],
['Two', category::NOT_EQUAL_TO, true, ['Category 1', 'One', 'Four', 'Five', 'Six']],
['Three', category::NOT_EQUAL_TO, true, ['Category 1', 'One', 'Two', 'Four', 'Five', 'Six']],

// Default/empty state.
[null, category::EQUAL_TO, false, ['Category 1', 'One', 'Two', 'Three', 'Four', 'Five', 'Six']],
];
}

/**
* Test getting filter SQL
*
* @param string|null $categoryname
* @param int $operator
* @param bool $subcategories
* @param string[] $expectedcategories
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(?string $categoryname, bool $subcategories, array $expectedcategories): void {
public function test_get_sql_filter(
?string $categoryname,
int $operator,
bool $subcategories,
array $expectedcategories,
): void {

global $DB;

$this->resetAfterTest();

// Create category tree "One -> Two -> Three".
$category1 = $this->getDataGenerator()->create_category(['name' => 'One']);
$category2 = $this->getDataGenerator()->create_category(['name' => 'Two', 'parent' => $category1->id]);
$category3 = $this->getDataGenerator()->create_category(['name' => 'Three', 'parent' => $category2->id]);

// Second category tree "Four -> Five -> Six".
$category4 = $this->getDataGenerator()->create_category(['name' => 'Four']);
$category5 = $this->getDataGenerator()->create_category(['name' => 'Five', 'parent' => $category4->id]);
$category6 = $this->getDataGenerator()->create_category(['name' => 'Six', 'parent' => $category5->id]);

if ($categoryname !== null) {
$categoryid = $DB->get_field('course_categories', 'id', ['name' => $categoryname], MUST_EXIST);
} else {
Expand All @@ -82,6 +104,7 @@ public function test_get_sql_filter(?string $categoryname, bool $subcategories,

// Create instance of our filter, passing given operator.
[$select, $params] = category::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $categoryid,
$filter->get_unique_identifier() . '_subcategories' => $subcategories,
]);
Expand Down

0 comments on commit 501a170

Please sign in to comment.