From bd0b6d869323b6db7ebd41242f176cee4bf8a133 Mon Sep 17 00:00:00 2001 From: Michael Hawkins Date: Wed, 3 Oct 2018 12:18:11 +0800 Subject: [PATCH] MDL-63510 mod_survey: Add support for removal of context users This issue is a part of the MDL-62560 Epic. --- mod/survey/classes/privacy/provider.php | 96 +++++++++++++++++++++++++++ mod/survey/tests/privacy_test.php | 112 ++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/mod/survey/classes/privacy/provider.php b/mod/survey/classes/privacy/provider.php index 44465643aa6..b545e4b7883 100644 --- a/mod/survey/classes/privacy/provider.php +++ b/mod/survey/classes/privacy/provider.php @@ -31,8 +31,10 @@ use context_helper; use context_module; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\approved_userlist; use core_privacy\local\request\helper; use core_privacy\local\request\transform; +use core_privacy\local\request\userlist; use core_privacy\local\request\writer; require_once($CFG->dirroot . '/mod/survey/lib.php'); @@ -47,6 +49,7 @@ require_once($CFG->dirroot . '/mod/survey/lib.php'); */ class provider implements \core_privacy\local\metadata\provider, + \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** @@ -114,6 +117,61 @@ class provider implements } /** + * Get the list of users who have data within a context. + * + * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. + */ + public static function get_users_in_context(userlist $userlist) { + $context = $userlist->get_context(); + + if (!is_a($context, \context_module::class)) { + return; + } + + $params = [ + 'survey' => 'survey', + 'modulelevel' => CONTEXT_MODULE, + 'contextid' => $context->id, + ]; + + $sql = " + SELECT sa.userid + FROM {survey} s + JOIN {modules} m + ON m.name = :survey + JOIN {course_modules} cm + ON cm.instance = s.id + AND cm.module = m.id + JOIN {context} ctx + ON ctx.instanceid = cm.id + AND ctx.contextlevel = :modulelevel + JOIN {survey_answers} sa + ON sa.survey = s.id + WHERE ctx.id = :contextid + AND s.template <> 0"; + + $userlist->add_from_sql('userid', $sql, $params); + + $sql = " + SELECT sy.userid + FROM {survey} s + JOIN {modules} m + ON m.name = :survey + JOIN {course_modules} cm + ON cm.instance = s.id + AND cm.module = m.id + JOIN {context} ctx + ON ctx.instanceid = cm.id + AND ctx.contextlevel = :modulelevel + JOIN {survey_analysis} sy + ON sy.survey = s.id + WHERE ctx.id = :contextid + AND s.template <> 0"; + + $userlist->add_from_sql('userid', $sql, $params); + } + + /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. @@ -276,6 +334,44 @@ class provider implements } /** + * Delete multiple users within a single context. + * + * @param approved_userlist $userlist The approved context and user information to delete information for. + */ + public static function delete_data_for_users(approved_userlist $userlist) { + global $DB; + $context = $userlist->get_context(); + + if ($context->contextlevel != CONTEXT_MODULE) { + return; + } + + // Fetch the survey ID. + $sql = " + SELECT s.id + FROM {survey} s + JOIN {modules} m + ON m.name = :survey + JOIN {course_modules} cm + ON cm.instance = s.id + AND cm.module = m.id + WHERE cm.id = :cmid"; + $params = [ + 'survey' => 'survey', + 'cmid' => $context->instanceid, + ]; + $surveyid = $DB->get_field_sql($sql, $params); + $userids = $userlist->get_userids(); + + // Delete all the things. + list($insql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); + $params['surveyid'] = $surveyid; + + $DB->delete_records_select('survey_answers', "survey = :surveyid AND userid {$insql}", $params); + $DB->delete_records_select('survey_analysis', "survey = :surveyid AND userid {$insql}", $params); + } + + /** * Get a survey ID from its context. * * @param context_module $context The module context. diff --git a/mod/survey/tests/privacy_test.php b/mod/survey/tests/privacy_test.php index c3380730122..75ea2f2fe0a 100644 --- a/mod/survey/tests/privacy_test.php +++ b/mod/survey/tests/privacy_test.php @@ -29,6 +29,7 @@ global $CFG; use core_privacy\tests\provider_testcase; use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\approved_userlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use mod_survey\privacy\provider; @@ -85,6 +86,59 @@ class mod_survey_privacy_testcase extends provider_testcase { $this->assertTrue(in_array(context_module::instance($cm1c->cmid)->id, $contextids)); } + /** + * Test for provider::test_get_users_in_context(). + */ + public function test_get_users_in_context() { + $dg = $this->getDataGenerator(); + $component = 'mod_survey'; + + $c1 = $dg->create_course(); + $c2 = $dg->create_course(); + $cm1a = $dg->create_module('survey', ['template' => 1, 'course' => $c1]); + $cm1b = $dg->create_module('survey', ['template' => 2, 'course' => $c1]); + $cm2 = $dg->create_module('survey', ['template' => 1, 'course' => $c2]); + $cm1acontext = context_module::instance($cm1a->cmid); + $cm1bcontext = context_module::instance($cm1b->cmid); + $cm2context = context_module::instance($cm2->cmid); + + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + $bothusers = [$u1->id, $u2->id]; + sort($bothusers); + + $this->create_answer($cm1a->id, 1, $u1->id); + $this->create_answer($cm1b->id, 1, $u1->id); + $this->create_answer($cm1b->id, 1, $u2->id); + $this->create_answer($cm2->id, 1, $u2->id); + $this->create_analysis($cm2->id, $u1->id); + + // Cm1a should only contain u1. + $userlist = new \core_privacy\local\request\userlist($cm1acontext, $component); + provider::get_users_in_context($userlist); + + $this->assertCount(1, $userlist); + $this->assertEquals([$u1->id], $userlist->get_userids()); + + // Cm1b should contain u1 and u2 (both have answers). + $userlist = new \core_privacy\local\request\userlist($cm1bcontext, $component); + provider::get_users_in_context($userlist); + + $this->assertCount(2, $userlist); + $actual = $userlist->get_userids(); + sort($actual); + $this->assertEquals($bothusers, $actual); + + // Cm2 should contain u1 (analysis) and u2 (answer). + $userlist = new \core_privacy\local\request\userlist($cm2context, $component); + provider::get_users_in_context($userlist); + + $this->assertCount(2, $userlist); + $actual = $userlist->get_userids(); + sort($actual); + $this->assertEquals($bothusers, $actual); + } + public function test_delete_data_for_all_users_in_context() { global $DB; $dg = $this->getDataGenerator(); @@ -190,6 +244,64 @@ class mod_survey_privacy_testcase extends provider_testcase { $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1c->id])); } + /** + * Test for provider::delete_data_for_users(). + */ + public function test_delete_data_for_users() { + global $DB; + $dg = $this->getDataGenerator(); + $component = 'mod_survey'; + + $c1 = $dg->create_course(); + $cm1a = $dg->create_module('survey', ['template' => 1, 'course' => $c1]); + $cm1b = $dg->create_module('survey', ['template' => 2, 'course' => $c1]); + $cm1c = $dg->create_module('survey', ['template' => 2, 'course' => $c1]); + $cm1acontext = context_module::instance($cm1a->cmid); + $cm1bcontext = context_module::instance($cm1b->cmid); + + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + + $this->create_answer($cm1a->id, 1, $u1->id); + $this->create_answer($cm1a->id, 1, $u2->id); + $this->create_analysis($cm1a->id, $u1->id); + $this->create_analysis($cm1a->id, $u2->id); + $this->create_answer($cm1b->id, 1, $u2->id); + $this->create_analysis($cm1b->id, $u1->id); + $this->create_answer($cm1c->id, 1, $u1->id); + $this->create_analysis($cm1c->id, $u2->id); + + // Confirm data exists before deletion. + $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1a->id])); + $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1c->id])); + $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u2->id, 'survey' => $cm1a->id])); + $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u2->id, 'survey' => $cm1b->id])); + $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u1->id, 'survey' => $cm1a->id])); + $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u1->id, 'survey' => $cm1b->id])); + $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1a->id])); + $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1c->id])); + + // Ensure only approved user data is deleted. + $approveduserids = [$u1->id]; + $approvedlist = new approved_userlist($cm1acontext, $component, $approveduserids); + provider::delete_data_for_users($approvedlist); + + $this->assertFalse($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1a->id])); + $this->assertFalse($DB->record_exists('survey_analysis', ['userid' => $u1->id, 'survey' => $cm1a->id])); + $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u2->id, 'survey' => $cm1a->id])); + $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1a->id])); + + $approveduserids = [$u1->id, $u2->id]; + $approvedlist = new approved_userlist($cm1bcontext, $component, $approveduserids); + provider::delete_data_for_users($approvedlist); + + $this->assertFalse($DB->record_exists('survey_answers', ['survey' => $cm1b->id])); + $this->assertFalse($DB->record_exists('survey_analysis', ['survey' => $cm1b->id])); + + $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1c->id])); + $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1c->id])); + } + public function test_export_data_for_user() { global $DB; $dg = $this->getDataGenerator(); -- 2.11.4.GIT