diff --git a/admin/tool/dataprivacy/classes/external/get_data_requests.php b/admin/tool/dataprivacy/classes/external/get_data_requests.php new file mode 100644 index 00000000000..e060cd4f2d6 --- /dev/null +++ b/admin/tool/dataprivacy/classes/external/get_data_requests.php @@ -0,0 +1,165 @@ +. + +namespace tool_dataprivacy\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_multiple_structure; +use core_external\external_value; +use core_external\external_warnings; +use tool_dataprivacy\api; +use core_user; +use context_system; +use moodle_exception; + +/** + * External function for getting data requests. + * + * @package tool_dataprivacy + * @copyright 2023 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 4.4 + */ +class get_data_requests extends external_api { + + /** + * Webservice parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters( + [ + 'userid' => new external_value(PARAM_INT, 'The id of the user to get the data requests for. Empty for all users.', + VALUE_DEFAULT, 0), + 'statuses' => new external_multiple_structure( + new external_value(PARAM_INT, 'The status of the data requests to get.'), + 'The statuses of the data requests to get. + 0 for pending 1 preprocessing, 2 awaiting approval, 3 approved, + 4 processed, 5 completed, 6 cancelled, 7 rejected.', + VALUE_DEFAULT, + [] + ), + 'types' => new external_multiple_structure( + new external_value(PARAM_INT, 'The type of the data requests to get.'), + 'The types of the data requests to get. 1 for export, 2 for data deletion.', + VALUE_DEFAULT, + [] + ), + 'creationmethods' => new external_multiple_structure( + new external_value(PARAM_INT, 'The creation method of the data requests to get.'), + 'The creation methods of the data requests to get. 0 for manual, 1 for automatic.', + VALUE_DEFAULT, + [] + ), + 'sort' => new external_value(PARAM_NOTAGS, 'The field to sort the data requests by.', + VALUE_DEFAULT, ''), + 'limitfrom' => new external_value(PARAM_INT, 'The number to start getting the data requests from.', + VALUE_DEFAULT, 0), + 'limitnum' => new external_value(PARAM_INT, 'The number of data requests to get.', + VALUE_DEFAULT, 0), + ] + ); + } + + + /** + * Get data requests. + * + * @param int $userid The user id. + * @param array $statuses The status filters. + * @param array $types The request type filters. + * @param array $creationmethods The request creation method filters. + * @param string $sort The order by clause. + * @param int $limitfrom Amount of records to skip. + * @param int $limitnum Amount of records to fetch. + * @throws moodle_exception + * @return array containing the data requests and warnings. + */ + public static function execute($userid = 0, $statuses = [], $types = [], $creationmethods = [], + $sort = '', $limitfrom = 0, $limitnum = 0): array { + + global $USER, $PAGE; + + $params = self::validate_parameters(self::execute_parameters(), [ + 'userid' => $userid, + 'statuses' => $statuses, + 'types' => $types, + 'creationmethods' => $creationmethods, + 'sort' => $sort, + 'limitfrom' => $limitfrom, + 'limitnum' => $limitnum, + ]); + $systemcontext = context_system::instance(); + + if ($params['userid'] == $USER->id) { + $userid = $USER->id; + } else { + // Additional security checks when obtaining data requests for other users. + if (!has_capability('tool/dataprivacy:managedatarequests', $systemcontext) || !api::is_site_dpo($USER->id)) { + $dponamestring = implode (', ', api::get_dpo_role_names()); + throw new moodle_exception('privacyofficeronly', 'tool_dataprivacy', '', $dponamestring); + } + + $userid = 0; + if (!empty($params['userid'])) { + $user = core_user::get_user($params['userid'], '*', MUST_EXIST); + core_user::require_active_user($user); + $userid = $user->id; + } + } + + // Ensure sort parameter is safe to use. Fallback to default value of the parameter itself. + $sortorderparts = explode(' ', $params['sort'], 2); + $sortorder = get_safe_orderby([ + 'id' => 'id', + 'status' => 'status', + 'timemodified' => 'timemodified', + 'default' => '', + ], $sortorderparts[0], $sortorderparts[1] ?? '', false); + + $userrequests = api::get_data_requests($userid, $params['statuses'], $params['types'], $params['creationmethods'], + $sortorder, $params['limitfrom'], $params['limitnum']); + + $requests = []; + foreach ($userrequests as $requestpersistent) { + $exporter = new data_request_exporter($requestpersistent, ['context' => $systemcontext]); + $renderer = $PAGE->get_renderer('tool_dataprivacy'); + $requests[] = $exporter->export($renderer); + } + + return [ + 'requests' => $requests, + 'warnings' => [], + ]; + } + + /** + * Webservice returns. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure( + [ + 'requests' => new external_multiple_structure(data_request_exporter::get_read_structure(), 'The data requests.'), + 'warnings' => new external_warnings(), + ] + ); + } +} diff --git a/admin/tool/dataprivacy/db/services.php b/admin/tool/dataprivacy/db/services.php index 2f8226a75bf..cbe56589751 100644 --- a/admin/tool/dataprivacy/db/services.php +++ b/admin/tool/dataprivacy/db/services.php @@ -255,4 +255,10 @@ $functions = [ 'type' => 'write', 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], ], + 'tool_dataprivacy_get_data_requests' => [ + 'classname' => '\tool_dataprivacy\external\get_data_requests', + 'description' => 'Gets data request.', + 'type' => 'read', + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ], ]; diff --git a/admin/tool/dataprivacy/tests/external/external_test.php b/admin/tool/dataprivacy/tests/external/external_test.php index 4db07195af1..d1b01feaa79 100644 --- a/admin/tool/dataprivacy/tests/external/external_test.php +++ b/admin/tool/dataprivacy/tests/external/external_test.php @@ -27,6 +27,7 @@ namespace tool_dataprivacy\external; use core_external\external_api; use externallib_advanced_testcase; use tool_dataprivacy\api; +use tool_dataprivacy\data_request; use tool_dataprivacy\context_instance; use tool_dataprivacy\external; @@ -1243,4 +1244,97 @@ class external_test extends externallib_advanced_testcase { $this->expectExceptionMessage(get_string('errorinvalidrequesttype', 'tool_dataprivacy')); create_data_request::execute(125, 'Example comment'); } + + /** + * Test for external::get_data_requests(). + */ + public function test_get_data_requests() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $anotheruser = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + // Empty results. + $result = get_data_requests::execute($user->id); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + + $this->assertEmpty($result['warnings']); + $this->assertEmpty($result['requests']); + + // Create data requests. + $comment = 'Example comment'; + $request1 = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE, $comment); + $request2 = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment); + $request3 = api::create_data_request($anotheruser->id, api::DATAREQUEST_TYPE_EXPORT, $comment, + data_request::DATAREQUEST_CREATION_AUTO); + + // Get data requests. + $result = get_data_requests::execute($user->id); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + + $this->assertEmpty($result['warnings']); + $this->assertCount(2, $result['requests']); + foreach ($result['requests'] as $request) { + if ($request['id'] == $request1->get('id')) { + $this->assertEquals($user->id, $request['userid']); + $this->assertEquals(api::DATAREQUEST_TYPE_DELETE, $request['type']); + $this->assertEquals($comment, $request['comments']); + } else { + $this->assertEquals($user->id, $request['userid']); + $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $request['type']); + $this->assertEquals($comment, $request['comments']); + } + } + + // Filter by type. + $result = get_data_requests::execute($user->id, [], [api::DATAREQUEST_TYPE_DELETE]); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + $this->assertCount(1, $result['requests']); + $this->assertEquals($request1->get('id'), $result['requests'][0]['id']); + + // Admin get all. + $this->setAdminUser(); + $result = get_data_requests::execute(); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + + $this->assertCount(3, $result['requests']); + + // Test limit. + $result = get_data_requests::execute(0, [], [], [], '', 2, 1); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + + $this->assertCount(1, $result['requests']); + + // Test sort. + $result = get_data_requests::execute(0, [], [], [], 'id DESC'); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + $this->assertEquals($request1->get('id'), $result['requests'][2]['id']); + $this->assertEquals($request2->get('id'), $result['requests'][1]['id']); + $this->assertEquals($request3->get('id'), $result['requests'][0]['id']); + + // Test filter by status. + api::update_request_status($request1->get('id'), api::DATAREQUEST_STATUS_DOWNLOAD_READY); + $result = get_data_requests::execute(0, [api::DATAREQUEST_STATUS_DOWNLOAD_READY]); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + + $this->assertCount(1, $result['requests']); + $this->assertEquals($request1->get('id'), $result['requests'][0]['id']); + + // Test filter by creation method. + $result = get_data_requests::execute(0, [], [], [data_request::DATAREQUEST_CREATION_AUTO]); + $result = external_api::clean_returnvalue(get_data_requests::execute_returns(), $result); + + $this->assertCount(1, $result['requests']); + $this->assertEquals($request3->get('id'), $result['requests'][0]['id']); + + // Get data requests for another user without required permissions. + $this->setUser($anotheruser); + + $this->expectException(\moodle_exception::class); + $dponamestring = implode (', ', api::get_dpo_role_names()); + $this->expectExceptionMessage(get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring)); + $result = get_data_requests::execute($user->id); + } + }