From bd9abebcd14579251a7fff953714c3efae92abf0 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 24 Sep 2021 22:46:45 +0100 Subject: [PATCH] MDL-72280 course: implement course datasource for custom reporting. Create new entity for exposing report data on course categories, joining it to the course entity to provide data for the reportbuilder editor. Co-authored-by: Carlos Castillo --- .../local/entities/course_category.php | 173 ++++++++++++++++++ .../reportbuilder/datasource/courses.php | 110 +++++++++++ course/tests/datasource_test.php | 69 +++++++ .../classes/local/entities/course.php | 4 +- 4 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 course/classes/local/entities/course_category.php create mode 100644 course/classes/reportbuilder/datasource/courses.php create mode 100644 course/tests/datasource_test.php diff --git a/course/classes/local/entities/course_category.php b/course/classes/local/entities/course_category.php new file mode 100644 index 00000000000..15191169b2e --- /dev/null +++ b/course/classes/local/entities/course_category.php @@ -0,0 +1,173 @@ +. + +declare(strict_types=1); + +namespace core_course\local\entities; + +use lang_string; +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\report\column; +use core_reportbuilder\local\report\filter; + +/** + * Course category entity + * + * @package core_course + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_category extends base { + + /** + * Database tables that this entity uses and their default aliases + * + * @return array + */ + protected function get_default_table_aliases(): array { + return [ + 'context' => 'ccctx', + 'course_categories' => 'cc', + ]; + } + + /** + * The default title for this entity + * + * @return lang_string + */ + protected function get_default_entity_title(): lang_string { + return new lang_string('coursecategory'); + } + + /** + * Initialise the entity + * + * @return base + */ + public function initialise(): base { + $columns = $this->get_all_columns(); + foreach ($columns as $column) { + $this->add_column($column); + } + + // All the filters defined by the entity can also be used as conditions. + $filters = $this->get_all_filters(); + foreach ($filters as $filter) { + $this + ->add_filter($filter) + ->add_condition($filter); + } + + return $this; + } + + /** + * Returns list of all available columns + * + * @return column[] + */ + protected function get_all_columns(): array { + $tablealias = $this->get_table_alias('course_categories'); + $tablealiascontext = $this->get_table_alias('context'); + + // Name column. + $columns[] = (new column( + 'name', + new lang_string('categoryname'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.name") + ->set_is_sortable(true); + + // ID number column. + $columns[] = (new column( + 'idnumber', + new lang_string('idnumbercoursecategory'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.idnumber") + ->set_is_sortable(true); + + // Description column (note we need to join/select from the context table in order to format the column). + $columns[] = (new column( + 'description', + new lang_string('description'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->add_join(" + JOIN {context} {$tablealiascontext} + ON {$tablealiascontext}.instanceid = {$tablealias}.id + AND {$tablealiascontext}.contextlevel = " . CONTEXT_COURSECAT) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.description, {$tablealias}.descriptionformat, {$tablealiascontext}.id AS contextid") + ->add_callback(static function(string $description, stdClass $category): string { + global $CFG; + require_once("{$CFG->libdir}/filelib.php"); + + $description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $category->contextid, 'coursecat', + 'description', null); + + return format_text($description, $category->descriptionformat, ['context' => $category->contextid]); + }) + ->set_is_sortable(false); + + return $columns; + } + + /** + * Return list of all available filters + * + * @return filter[] + */ + protected function get_all_filters(): array { + $tablealias = $this->get_table_alias('course_categories'); + + // Name filter. + $filters[] = (new filter( + select::class, + 'name', + new lang_string('categoryname'), + $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'); + }); + + // ID number filter. + $filters[] = (new filter( + text::class, + 'idnumber', + new lang_string('idnumbercoursecategory'), + $this->get_entity_name(), + "{$tablealias}.idnumber" + )) + ->add_joins($this->get_joins()); + + return $filters; + } +} diff --git a/course/classes/reportbuilder/datasource/courses.php b/course/classes/reportbuilder/datasource/courses.php new file mode 100644 index 00000000000..ebc56af0294 --- /dev/null +++ b/course/classes/reportbuilder/datasource/courses.php @@ -0,0 +1,110 @@ +. + +declare(strict_types=1); + +namespace core_course\reportbuilder\datasource; + +use core_course\local\entities\course_category; +use core_reportbuilder\datasource; +use core_reportbuilder\local\entities\course; +use core_reportbuilder\local\helpers\database; + +/** + * Courses datasource + * + * @package core_course + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class courses extends datasource { + + /** + * Return user friendly name of the datasource + * + * @return string + */ + public static function get_name(): string { + return get_string('courses'); + } + + /** + * Initialise report + */ + protected function initialise(): void { + $courseentity = new course(); + $coursetablealias = $courseentity->get_table_alias('course'); + + // Exclude site course. + $paramsiteid = database::generate_param_name(); + + $this->set_main_table('course', $coursetablealias); + $this->add_base_condition_sql("{$coursetablealias}.id != :{$paramsiteid}", [$paramsiteid => SITEID]); + + $this->add_entity($courseentity); + + // Join the course category entity. + $coursecatentity = new course_category(); + $coursecattablealias = $coursecatentity->get_table_alias('course_categories'); + $this->add_entity($coursecatentity + ->add_join("JOIN {course_categories} {$coursecattablealias} + ON {$coursecattablealias}.id = {$coursetablealias}.category")); + + // Add all columns from entities to be available in custom reports. + $this->add_columns_from_entity($coursecatentity->get_entity_name()); + $this->add_columns_from_entity($courseentity->get_entity_name()); + + // Add all filters from entities to be available in custom reports. + $this->add_filters_from_entity($coursecatentity->get_entity_name()); + $this->add_filters_from_entity($courseentity->get_entity_name()); + + // Add all conditions from entities to be available in custom reports. + $this->add_conditions_from_entity($coursecatentity->get_entity_name()); + $this->add_conditions_from_entity($courseentity->get_entity_name()); + } + + /** + * Return the columns that will be added to the report as part of default setup + * + * @return string[] + */ + public function get_default_columns(): array { + return [ + 'course_category:name', + 'course:shortname', + 'course:fullname', + 'course:idnumber', + ]; + } + + /** + * Return the filters that will be added to the report once is created + * + * @return string[] + */ + public function get_default_filters(): array { + return ['course_category:name', 'course:fullname', 'course:idnumber']; + } + + /** + * Return the conditions that will be added to the report once is created + * + * @return string[] + */ + public function get_default_conditions(): array { + return ['course_category:name']; + } +} diff --git a/course/tests/datasource_test.php b/course/tests/datasource_test.php new file mode 100644 index 00000000000..2d5c1a87389 --- /dev/null +++ b/course/tests/datasource_test.php @@ -0,0 +1,69 @@ +. + +declare(strict_types=1); + +namespace core_course\reportbuilder\datasource; + +use core_reportbuilder_testcase; +use core_reportbuilder_generator; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php"); + +/** + * Unit tests for component datasources + * + * @package core_course + * @covers \core_course\reportbuilder\datasource\courses + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class datasource_test extends core_reportbuilder_testcase { + + /** + * Test courses datasource + */ + public function test_courses_datasource(): void { + $this->resetAfterTest(); + + // Test subject. + $category = $this->getDataGenerator()->create_category(['name' => 'My cats']); + $course = $this->getDataGenerator()->create_course([ + 'category' => $category->id, + 'fullname' => 'All about cats', + 'shortname' => 'C101', + 'idnumber' => 'CAT101' + ]); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'Courses', 'source' => courses::class]); + + $content = $this->get_custom_report_content($report->get('id')); + $this->assertCount(1, $content); + + $contentrow = array_values(reset($content)); + $this->assertEquals([ + 'My cats', // Category name. + 'C101', // Course shortname. + 'All about cats', // Course fullname. + 'CAT101', // Course ID number. + ], $contentrow); + } +} diff --git a/reportbuilder/classes/local/entities/course.php b/reportbuilder/classes/local/entities/course.php index dd0989f2f79..6fe60c82eb3 100644 --- a/reportbuilder/classes/local/entities/course.php +++ b/reportbuilder/classes/local/entities/course.php @@ -107,7 +107,9 @@ class course extends base { $filters = array_merge($this->get_all_filters(), $customfields->get_filters()); foreach ($filters as $filter) { - $this->add_filter($filter); + $this + ->add_condition($filter) + ->add_filter($filter); } return $this;