MDL-78879 reportbuilder: allow for negation of category filter.

Add "Equal to" and "Not equal to" operators to the filter class.

AMOS BEGIN
 CPY [subcats,qtype_randomsamatch],[includesubcategories,moodle]
AMOS END
This commit is contained in:
Paul Holden 2023-08-02 12:26:54 +01:00
parent a1d5d1b2f7
commit 501a170cb6
No known key found for this signature in database
GPG Key ID: A81A96D6045F6164
4 changed files with 69 additions and 9 deletions

View File

@ -22,6 +22,7 @@ use context_course;
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;
@ -219,6 +220,11 @@ class courses_test extends core_reportbuilder_testcase {
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', [

View File

@ -1118,6 +1118,7 @@ $string['includeneededusers'] = 'Include needed users';
$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';

View File

@ -19,6 +19,7 @@ declare(strict_types=1);
namespace core_reportbuilder\local\filters;
use core_course_category;
use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
@ -36,20 +37,43 @@ use core_reportbuilder\local\helpers\database;
*/
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'));
}
/**
@ -63,6 +87,7 @@ class category extends base {
[$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"]);
@ -92,6 +117,11 @@ class category extends base {
)";
}
// If specified "Not equal to", then negate the entire clause.
if ($operator === self::NOT_EQUAL_TO) {
$sql = "NOT ({$sql})";
}
return [$sql, $params];
}

View File

@ -40,11 +40,20 @@ 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']],
];
}
@ -52,20 +61,33 @@ class category_test extends advanced_testcase {
* 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 {
@ -82,6 +104,7 @@ class category_test extends advanced_testcase {
// 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,
]);