From 28cf1fa8a1532144745dd52a1ba462eca4e0916b Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 24 Jun 2022 09:22:02 +0100 Subject: [PATCH] MDL-74955 reportbuilder: external method for listing custom reports. --- lib/db/services.php | 6 + .../custom_report_details_exporter.php | 80 +++++++++++++ .../classes/external/reports/listing.php | 113 ++++++++++++++++++ .../custom_report_details_exporter_test.php | 64 ++++++++++ .../tests/external/reports/listing_test.php | 92 ++++++++++++++ reportbuilder/upgrade.txt | 1 + version.php | 2 +- 7 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 reportbuilder/classes/external/custom_report_details_exporter.php create mode 100644 reportbuilder/classes/external/reports/listing.php create mode 100644 reportbuilder/tests/external/custom_report_details_exporter_test.php create mode 100644 reportbuilder/tests/external/reports/listing_test.php diff --git a/lib/db/services.php b/lib/db/services.php index b8f4075f442..7da191981ae 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -2812,6 +2812,12 @@ $functions = array( 'type' => 'read', 'ajax' => true, ], + 'core_reportbuilder_list_reports' => [ + 'classname' => 'core_reportbuilder\external\reports\listing', + 'description' => 'List custom reports for current user', + 'type' => 'read', + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ], 'core_reportbuilder_view_report' => [ 'classname' => 'core_reportbuilder\external\reports\view', 'description' => 'Trigger custom report viewed', diff --git a/reportbuilder/classes/external/custom_report_details_exporter.php b/reportbuilder/classes/external/custom_report_details_exporter.php new file mode 100644 index 00000000000..1cf896987c5 --- /dev/null +++ b/reportbuilder/classes/external/custom_report_details_exporter.php @@ -0,0 +1,80 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external; + +use core_user; +use renderer_base; +use core\external\persistent_exporter; +use core_reportbuilder\datasource; +use core_reportbuilder\manager; +use core_reportbuilder\local\models\report; +use core_user\external\user_summary_exporter; + +/** + * Custom report details exporter class + * + * @package core_reportbuilder + * @copyright 2022 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_details_exporter extends persistent_exporter { + + /** @var report The persistent object we will export. */ + protected $persistent = null; + + /** + * Return the name of the class we are exporting + * + * @return string + */ + protected static function define_class(): string { + return report::class; + } + + /** + * Return a list of additional properties used only for display + * + * @return array + */ + protected static function define_other_properties(): array { + return [ + 'sourcename' => [ + 'type' => PARAM_RAW, + 'null' => NULL_ALLOWED, + ], + 'modifiedby' => ['type' => user_summary_exporter::read_properties_definition()], + ]; + } + + /** + * Get additional values to inject while exporting + * + * @param renderer_base $output + * @return array + */ + protected function get_other_values(renderer_base $output): array { + $source = $this->persistent->get('source'); + $usermodified = core_user::get_user($this->persistent->get('usermodified')); + + return [ + 'sourcename' => manager::report_source_exists($source, datasource::class) ? $source::get_name() : null, + 'modifiedby' => (new user_summary_exporter($usermodified))->export($output), + ]; + } +} diff --git a/reportbuilder/classes/external/reports/listing.php b/reportbuilder/classes/external/reports/listing.php new file mode 100644 index 00000000000..e3509a6fec0 --- /dev/null +++ b/reportbuilder/classes/external/reports/listing.php @@ -0,0 +1,113 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\reports; + +use context_system; +use external_api; +use external_function_parameters; +use external_multiple_structure; +use external_single_structure; +use external_value; +use external_warnings; +use stdClass; +use core_reportbuilder\permission; +use core_reportbuilder\external\custom_report_details_exporter; +use core_reportbuilder\local\helpers\audience; +use core_reportbuilder\local\models\report; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->libdir}/externallib.php"); + +/** + * External method for listing users' custom reports + * + * @package core_reportbuilder + * @copyright 2022 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class listing extends external_api { + + /** + * External method parameters + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'page' => new external_value(PARAM_INT, 'Page number', VALUE_DEFAULT, 0), + 'perpage' => new external_value(PARAM_INT, 'Reports per page', VALUE_DEFAULT, 10), + ]); + } + + /** + * External method execution + * + * @param int $page + * @param int $perpage + * @return array + */ + public static function execute(int $page = 0, int $perpage = 10): array { + global $DB, $PAGE; + + [ + 'page' => $page, + 'perpage' => $perpage, + ] = self::validate_parameters(self::execute_parameters(), [ + 'page' => $page, + 'perpage' => $perpage, + ]); + + $context = context_system::instance(); + self::validate_context($context); + + permission::require_can_view_reports_list(null, $context); + + // Filter list of reports by those the user can access. + [$where, $params] = audience::user_reports_list_access_sql('r'); + $reports = $DB->get_records_sql(" + SELECT r.* + FROM {" . report::TABLE . "} r + WHERE r.type = 0 AND {$where} + ORDER BY r.name, r.id", $params, $page * $perpage, $perpage); + + $output = $PAGE->get_renderer('core'); + + return [ + 'reports' => array_map(static function(stdClass $report) use ($output): array { + $exporter = new custom_report_details_exporter(new report(0, $report)); + return (array) $exporter->export($output); + }, $reports), + 'warnings' => [], + ]; + } + + /** + * External method return value + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'reports' => new external_multiple_structure(custom_report_details_exporter::get_read_structure()), + 'warnings' => new external_warnings(), + ]); + } +} diff --git a/reportbuilder/tests/external/custom_report_details_exporter_test.php b/reportbuilder/tests/external/custom_report_details_exporter_test.php new file mode 100644 index 00000000000..25b74f34a0f --- /dev/null +++ b/reportbuilder/tests/external/custom_report_details_exporter_test.php @@ -0,0 +1,64 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external; + +use advanced_testcase; +use core_reportbuilder_generator; +use core_user\reportbuilder\datasource\users; + +/** + * Unit tests for custom report details exporter + * + * @package core_reportbuilder + * @covers \core_reportbuilder\external\custom_report_details_exporter + * @copyright 2022 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class custom_report_details_exporter_test extends advanced_testcase { + + /** + * Test exported data structure + */ + public function test_export(): void { + global $PAGE; + + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); + + $exporter = new custom_report_details_exporter($report); + $export = $exporter->export($PAGE->get_renderer('core_reportbuilder')); + + // The exporter outputs the persistent details, plus two other properties. + $this->assertEquals($report->get('name'), $export->name); + $this->assertEquals($report->get('source'), $export->source); + + // Source name should be the name of the source. + $this->assertEquals(users::get_name(), $export->sourcename); + + // We use the user exporter for the modifier of the report. + $this->assertObjectHasAttribute('modifiedby', $export); + $this->assertEquals(fullname($user), $export->modifiedby->fullname); + } +} diff --git a/reportbuilder/tests/external/reports/listing_test.php b/reportbuilder/tests/external/reports/listing_test.php new file mode 100644 index 00000000000..cb81798c5d8 --- /dev/null +++ b/reportbuilder/tests/external/reports/listing_test.php @@ -0,0 +1,92 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\external\reports; + +use context_system; +use core_reportbuilder_generator; +use external_api; +use externallib_advanced_testcase; +use core_reportbuilder\report_access_exception; +use core_reportbuilder\local\models\report; +use core_user\reportbuilder\datasource\users; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("{$CFG->dirroot}/webservice/tests/helpers.php"); + +/** + * Unit tests of external class for listing reports + * + * @package core_reportbuilder + * @covers \core_reportbuilder\external\reports\listing + * @copyright 2022 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class listing_test extends externallib_advanced_testcase { + + /** + * Text execute method + */ + public function test_execute(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + + // Create three reports. + $reportone = $generator->create_report(['name' => 'Report one', 'source' => users::class]); + $reporttwo = $generator->create_report(['name' => 'Report two', 'source' => users::class]); + $reportthree = $generator->create_report(['name' => 'Report three', 'source' => users::class]); + + // Create second user, with audience of both report one and two. + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $generator->create_audience(['reportid' => $reportone->get('id'), 'configdata' => []]); + $generator->create_audience(['reportid' => $reporttwo->get('id'), 'configdata' => []]); + + // Switch to second user, get their report listing. + $result = listing::execute(); + $result = external_api::clean_returnvalue(listing::execute_returns(), $result); + + $this->assertEquals(['Report one', 'Report two'], array_column($result['reports'], 'name')); + $this->assertEmpty($result['warnings']); + } + + /** + * Test execute method for a user without permission to view reports + */ + public function test_execute_access_exception(): void { + global $DB; + + $this->resetAfterTest(); + + $userrole = $DB->get_field('role', 'id', ['shortname' => 'user'], MUST_EXIST); + assign_capability('moodle/reportbuilder:view', CAP_PROHIBIT, $userrole, context_system::instance(), true); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->expectException(report_access_exception::class); + $this->expectExceptionMessage('You cannot view this report'); + listing::execute(); + } +} diff --git a/reportbuilder/upgrade.txt b/reportbuilder/upgrade.txt index 36cd7a716ec..d8f6b381090 100644 --- a/reportbuilder/upgrade.txt +++ b/reportbuilder/upgrade.txt @@ -10,6 +10,7 @@ Information provided here is intended especially for developers. * The external `core_reportbuilder_filters_reset` method now accepts an optional `parameters` argument, required by some system reports * New external methods for retrieving custom report data: + - `core_reportbuilder_list_reports` - `core_reportbuilder_view_report` * 'set_default_per_page' and 'get_default_per_page' methods have been added to \local\report\base class to manage the default displayed rows per page. diff --git a/version.php b/version.php index 24403243328..46afb702c1d 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022093000.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2022093000.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.1dev (Build: 20220930)'; // Human-friendly version name