diff --git a/course/classes/local/entities/course_category.php b/course/classes/local/entities/course_category.php index 6c923b0058aa9..6108a0a62287f 100644 --- a/course/classes/local/entities/course_category.php +++ b/course/classes/local/entities/course_category.php @@ -22,8 +22,7 @@ use stdClass; use core_course_category; use core_reportbuilder\local\entities\base; -use core_reportbuilder\local\filters\select; -use core_reportbuilder\local\filters\text; +use core_reportbuilder\local\filters\{category, text}; use core_reportbuilder\local\report\column; use core_reportbuilder\local\report\filter; @@ -167,18 +166,28 @@ protected function get_all_columns(): array { protected function get_all_filters(): array { $tablealias = $this->get_table_alias('course_categories'); - // Name filter. + // Select category filter. $filters[] = (new filter( - select::class, + category::class, 'name', - new lang_string('categoryname'), + new lang_string('categoryselect', 'core_reportbuilder'), $this->get_entity_name(), "{$tablealias}.id" )) ->add_joins($this->get_joins()) - ->set_options_callback(static function(): array { - return core_course_category::make_categories_list('moodle/category:viewcourselist'); - }); + ->set_options([ + 'requiredcapabilities' => 'moodle/category:viewcourselist', + ]); + + // Name filter. + $filters[] = (new filter( + text::class, + 'text', + new lang_string('categoryname'), + $this->get_entity_name(), + "{$tablealias}.name" + )) + ->add_joins($this->get_joins()); // ID number filter. $filters[] = (new filter( diff --git a/lang/en/reportbuilder.php b/lang/en/reportbuilder.php index cfbef1e7fd26b..1d7b376f4f657 100644 --- a/lang/en/reportbuilder.php +++ b/lang/en/reportbuilder.php @@ -56,6 +56,7 @@ $string['cardviewfirstcolumntitle'] = 'First column title'; $string['cardviewsettingssaved'] = 'Card view settings saved'; $string['cardviewvisiblecolumns'] = 'Columns visible'; +$string['categoryselect'] = 'Select category'; $string['close'] = 'Close'; $string['closeeditor'] = 'Close \'{$a}\' editor'; $string['columnadded'] = 'Added column \'{$a}\''; diff --git a/reportbuilder/classes/local/filters/category.php b/reportbuilder/classes/local/filters/category.php new file mode 100644 index 0000000000000..223634f74715f --- /dev/null +++ b/reportbuilder/classes/local/filters/category.php @@ -0,0 +1,93 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\filters; + +use core_course_category; +use MoodleQuickForm; +use core_reportbuilder\local\helpers\database; + +/** + * Course category report filter + * + * The following optional array property can be passed to the {@see \core_reportbuilder\local\report\filter::set_options} method + * when defining this filter, to define the capabilities passed to {@see \core_course_category::make_categories_list} + * + * ['requiredcapabilities' => '...'] + * + * @package core_reportbuilder + * @copyright 2022 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class category extends base { + + /** + * Setup form + * + * @param MoodleQuickForm $mform + */ + public function setup_form(MoodleQuickForm $mform): void { + $label = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()); + + // 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')); + } + + /** + * Return filter SQL + * + * @param array $values + * @return array + */ + public function get_sql_filter(array $values): array { + global $DB; + + $fieldsql = $this->filter->get_field_sql(); + $params = $this->filter->get_field_params(); + + $category = (int) ($values["{$this->name}_value"] ?? 0); + $subcategories = !empty($values["{$this->name}_subcategories"]); + + // Invalid or inactive filter. + if (empty($category)) { + return ['', []]; + } + + // Initial matching on selected category. + $paramcategory = database::generate_param_name(); + $params[$paramcategory] = $category; + $sql = "{$fieldsql} = :{$paramcategory}"; + + // Sub-category matching on path of selected category. + if ($subcategories) { + $paramcategorypath = database::generate_param_name(); + $params[$paramcategorypath] = "%/{$category}/%"; + $sql .= " OR {$fieldsql} IN ( + SELECT id + FROM {course_categories} + WHERE " . $DB->sql_like('path', ":{$paramcategorypath}") . " + )"; + } + + return [$sql, $params]; + } +} diff --git a/reportbuilder/tests/external/custom_report_conditions_exporter_test.php b/reportbuilder/tests/external/custom_report_conditions_exporter_test.php index c11f3f4cfd328..5a55f01d5266b 100644 --- a/reportbuilder/tests/external/custom_report_conditions_exporter_test.php +++ b/reportbuilder/tests/external/custom_report_conditions_exporter_test.php @@ -62,7 +62,7 @@ public function test_export(): void { $this->assertGreaterThanOrEqual(1, $conditionscategory['optiongroup']['values']); $this->assertEquals([ 'value' => 'course_category:name', - 'visiblename' => 'Category name', + 'visiblename' => 'Select category', ], $conditionscategory['optiongroup']['values'][0]); // Course conditions, assert structure of first item. @@ -76,6 +76,7 @@ public function test_export(): void { // Make sure the active condition we added, isn't present in available conditions. $this->assertNotContains('course:shortname', array_column($conditionscourse['optiongroup']['values'], 'value')); + // The active conditions are contained inside form HTML, just assert there's something present. $this->assertTrue($export->hasactiveconditions); $this->assertNotEmpty($export->activeconditionsform); $this->assertNotEmpty($export->helpicon); diff --git a/reportbuilder/tests/external/custom_report_filters_exporter_test.php b/reportbuilder/tests/external/custom_report_filters_exporter_test.php index d3f3150212ef0..8f76cd408eb6f 100644 --- a/reportbuilder/tests/external/custom_report_filters_exporter_test.php +++ b/reportbuilder/tests/external/custom_report_filters_exporter_test.php @@ -74,8 +74,8 @@ public function test_export(): void { $this->assertEquals('Course category', $filterscategory['optiongroup']['text']); $this->assertGreaterThanOrEqual(1, $filterscategory['optiongroup']['values']); $this->assertEquals([ - 'value' => 'course_category:idnumber', - 'visiblename' => 'Category ID number', + 'value' => 'course_category:text', + 'visiblename' => 'Category name', ], $filterscategory['optiongroup']['values'][0]); // Course filters, assert structure of first item. @@ -104,7 +104,7 @@ public function test_export(): void { // Course category filter. $this->assertEquals($filtercategoryname->get('id'), $activefiltercategoryname['id']); $this->assertEquals('Course category', $activefiltercategoryname['entityname']); - $this->assertEquals('Category name', $activefiltercategoryname['heading']); + $this->assertEquals('Select category', $activefiltercategoryname['heading']); $this->assertEquals(2, $activefiltercategoryname['sortorder']); $this->assertNotEmpty($export->helpicon); diff --git a/reportbuilder/tests/local/filters/category_test.php b/reportbuilder/tests/local/filters/category_test.php new file mode 100644 index 0000000000000..819d158a03f83 --- /dev/null +++ b/reportbuilder/tests/local/filters/category_test.php @@ -0,0 +1,91 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\filters; + +use advanced_testcase; +use lang_string; +use core_reportbuilder\local\report\filter; + +/** + * Unit tests for course category report filter + * + * @package core_reportbuilder + * @covers \core_reportbuilder\local\filters\category + * @copyright 2022 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class category_test extends advanced_testcase { + + /** + * Data provider for {@see test_get_sql_filter} + * + * @return array + */ + 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']], + ]; + } + + /** + * Test getting filter SQL + * + * @param string|null $categoryname + * @param bool $subcategories + * @param string[] $expectedcategories + * + * @dataProvider get_sql_filter_provider + */ + public function test_get_sql_filter(?string $categoryname, bool $subcategories, array $expectedcategories): void { + global $DB; + + $this->resetAfterTest(); + + $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]); + + if ($categoryname !== null) { + $categoryid = $DB->get_field('course_categories', 'id', ['name' => $categoryname], MUST_EXIST); + } else { + $categoryid = null; + } + + $filter = new filter( + category::class, + 'test', + new lang_string('yes'), + 'testentity', + 'id' + ); + + // Create instance of our filter, passing given operator. + [$select, $params] = category::create($filter)->get_sql_filter([ + $filter->get_unique_identifier() . '_value' => $categoryid, + $filter->get_unique_identifier() . '_subcategories' => $subcategories, + ]); + + $categories = $DB->get_fieldset_select('course_categories', 'name', $select, $params); + $this->assertEqualsCanonicalizing($expectedcategories, $categories); + } +} diff --git a/reportbuilder/upgrade.txt b/reportbuilder/upgrade.txt index 3c769a2da3380..bde3cd2c67a0d 100644 --- a/reportbuilder/upgrade.txt +++ b/reportbuilder/upgrade.txt @@ -19,4 +19,6 @@ Information provided here is intended especially for developers. - `[require_]can_create_report` * New method `get_default_condition_values()` in base datasource class, to be overridden by sources that wish to define default values for conditions upon report creation. -* New `tags` filter type for reports that contain entities with support for core_tag API +* New report filter types: + - `category` for reports containing course categories + - `tags` for reports containing entities with support for core_tag API