From 541982c01e87eed7efee36fd09945276abb45d70 Mon Sep 17 00:00:00 2001 From: Mihail Geshoski Date: Tue, 30 Oct 2018 16:41:44 +0800 Subject: [PATCH] MDL-63606 core_badges: Add support for removal of context users This issue is part of the MDL-62560 Epic. --- badges/classes/privacy/provider.php | 94 +++++++++++++++ badges/tests/privacy_test.php | 172 ++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+) diff --git a/badges/classes/privacy/provider.php b/badges/classes/privacy/provider.php index 1b198cb1be1..ab3c83d2301 100644 --- a/badges/classes/privacy/provider.php +++ b/badges/classes/privacy/provider.php @@ -24,6 +24,7 @@ */ namespace core_badges\privacy; + defined('MOODLE_INTERNAL') || die(); use badge; @@ -37,6 +38,8 @@ use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; +use core_privacy\local\request\userlist; +use core_privacy\local\request\approved_userlist; require_once($CFG->libdir . '/badgeslib.php'); @@ -50,6 +53,7 @@ require_once($CFG->libdir . '/badgeslib.php'); */ class provider implements \core_privacy\local\metadata\provider, + \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\subsystem\provider { /** @@ -173,6 +177,78 @@ class provider implements return $contextlist; } + /** + * Get the list of users within a specific 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(); + + $allowedcontexts = [ + CONTEXT_COURSE, + CONTEXT_SYSTEM, + CONTEXT_USER + ]; + + if (!in_array($context->contextlevel, $allowedcontexts)) { + return; + } + + if ($context->contextlevel == CONTEXT_COURSE || $context->contextlevel == CONTEXT_SYSTEM) { + // Find the modifications we made on badges (course & system). + $params = [ + 'courselevel' => CONTEXT_COURSE, + 'syscontextid' => SYSCONTEXTID, + 'typecourse' => BADGE_TYPE_COURSE, + 'typesite' => BADGE_TYPE_SITE, + 'contextid' => $context->id, + ]; + + $sql = "SELECT b.usermodified, b.usercreated + FROM {badge} b + JOIN {context} ctx + ON (b.type = :typecourse AND b.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel) + OR (b.type = :typesite AND ctx.id = :syscontextid) + WHERE ctx.id = :contextid"; + + $userlist->add_from_sql('usermodified', $sql, $params); + $userlist->add_from_sql('usercreated', $sql, $params); + } + + if ($context->contextlevel == CONTEXT_USER) { + // Find where we've manually awarded a badge (recipient user context). + $params = [ + 'instanceid' => $context->instanceid + ]; + + $sql = "SELECT issuerid, recipientid + FROM {badge_manual_award} + WHERE recipientid = :instanceid"; + + $userlist->add_from_sql('issuerid', $sql, $params); + $userlist->add_from_sql('recipientid', $sql, $params); + + $sql = "SELECT userid + FROM {badge_issued} + WHERE userid = :instanceid"; + + $userlist->add_from_sql('userid', $sql, $params); + + $sql = "SELECT userid + FROM {badge_criteria_met} + WHERE userid = :instanceid"; + + $userlist->add_from_sql('userid', $sql, $params); + + $sql = "SELECT userid + FROM {badge_backpack} + WHERE userid = :instanceid"; + + $userlist->add_from_sql('userid', $sql, $params); + } + } + /** * Export all user data for the specified user, in the specified contexts. * @@ -439,6 +515,24 @@ class provider implements static::delete_user_data($context->instanceid); } + /** + * 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) { + $context = $userlist->get_context(); + + if (!in_array($context->instanceid, $userlist->get_userids())) { + return; + } + + if ($context->contextlevel == CONTEXT_USER) { + // We can only delete our own data in the user context, nothing in course or system. + static::delete_user_data($context->instanceid); + } + } + /** * Delete all user data for the specified user, in the specified contexts. * diff --git a/badges/tests/privacy_test.php b/badges/tests/privacy_test.php index bf3b084d381..1bfb93abbe1 100644 --- a/badges/tests/privacy_test.php +++ b/badges/tests/privacy_test.php @@ -32,6 +32,7 @@ use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use core_badges\privacy\provider; +use core_privacy\local\request\approved_userlist; require_once($CFG->libdir . '/badgeslib.php'); @@ -399,6 +400,177 @@ class core_badges_privacy_testcase extends provider_testcase { $this->assertEquals('Manager', $data->badges[0]['issuer_role']); } + /** + * Test that only users within a user, system and course context are fetched. + */ + public function test_get_users_in_context() { + $component = 'core_badges'; + + // Create course1. + $course1 = $this->getDataGenerator()->create_course(); + $coursecontext1 = context_course::instance($course1->id); + // Create course2. + $course2 = $this->getDataGenerator()->create_course(); + $coursecontext2 = context_course::instance($course2->id); + // Create user1. + $user1 = $this->getDataGenerator()->create_user(); + $usercontext1 = context_user::instance($user1->id); + // Create user2. + $user2 = $this->getDataGenerator()->create_user(); + $usercontext2 = context_user::instance($user2->id); + // Create user3. + $user3 = $this->getDataGenerator()->create_user(); + $usercontext3 = context_user::instance($user3->id); + + // The list of users in usercontext1 should not return anything yet (related data still haven't been created). + $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(0, $userlist1); + // The list of users in coursecontext1 should not return anything yet (related data still haven't been created). + $userlist2 = new \core_privacy\local\request\userlist($coursecontext1, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(0, $userlist2); + // The list of users in systemcontext should not return anything yet (related data still haven't been created). + $systemcontext = context_system::instance(); + $userlist3 = new \core_privacy\local\request\userlist($systemcontext, $component); + provider::get_users_in_context($userlist3); + $this->assertCount(0, $userlist3); + + // Assert that we find contexts where we created/modified a badge. + $this->create_badge(['usercreated' => $user1->id, 'usermodified' => $user2->id]); + $badge1 = $this->create_badge(['usercreated' => $user2->id, 'type' => BADGE_TYPE_COURSE, 'courseid' => $course1->id]); + $badge2 = $this->create_badge(['usercreated' => $user3->id, 'usermodified' => $user1->id]); + + $this->create_manual_award(['recipientid' => $user2->id, 'issuerid' => $user1->id, 'badgeid' => $badge1->id]); + $this->create_manual_award(['recipientid' => $user3->id, 'issuerid' => $user2->id, 'badgeid' => $badge1->id]); + $this->create_manual_award(['recipientid' => $user1->id, 'issuerid' => $user2->id, 'badgeid' => $badge2->id]); + + $this->create_backpack(['userid' => $user2->id]); + $this->create_issued(['badgeid' => $badge2->id, 'userid' => $user3->id]); + + $crit = $this->create_criteria_manual($badge1->id); + $crit->mark_complete($user3->id); + + // The list of users for user context should return user1 and user2. + provider::get_users_in_context($userlist1); + $this->assertCount(2, $userlist1); + $this->assertTrue(in_array($user1->id, $userlist1->get_userids())); + $this->assertTrue(in_array($user2->id, $userlist1->get_userids())); + + // The list of users for course context should return user2. + provider::get_users_in_context($userlist2); + $this->assertCount(1, $userlist2); + $this->assertTrue(in_array($user2->id, $userlist2->get_userids())); + + // The list of users for system context should return user1, user2 and user3. + provider::get_users_in_context($userlist3); + $this->assertCount(3, $userlist3); + $this->assertTrue(in_array($user1->id, $userlist3->get_userids())); + $this->assertTrue(in_array($user2->id, $userlist3->get_userids())); + $this->assertTrue(in_array($user3->id, $userlist3->get_userids())); + } + + /** + * Test that data for users in approved userlist is deleted. + */ + public function test_delete_data_for_users() { + $component = 'core_badges'; + + // Create course1. + $course1 = $this->getDataGenerator()->create_course(); + $coursecontext1 = context_course::instance($course1->id); + // Create course2. + $course2 = $this->getDataGenerator()->create_course(); + $coursecontext2 = context_course::instance($course2->id); + // Create user1. + $user1 = $this->getDataGenerator()->create_user(); + $usercontext1 = context_user::instance($user1->id); + // Create user2. + $user2 = $this->getDataGenerator()->create_user(); + $usercontext2 = context_user::instance($user2->id); + // Create user3. + $user3 = $this->getDataGenerator()->create_user(); + $usercontext3 = context_user::instance($user3->id); + + $this->create_badge(['usercreated' => $user1->id, 'usermodified' => $user2->id]); + $badge1 = $this->create_badge(['usercreated' => $user2->id, 'type' => BADGE_TYPE_COURSE, 'courseid' => $course1->id]); + $badge2 = $this->create_badge(['usercreated' => $user3->id, 'type' => BADGE_TYPE_COURSE, 'courseid' => $course2->id, + 'usermodified' => $user1->id]); + + $this->create_manual_award(['recipientid' => $user2->id, 'issuerid' => $user1->id, 'badgeid' => $badge1->id]); + $this->create_manual_award(['recipientid' => $user3->id, 'issuerid' => $user2->id, 'badgeid' => $badge1->id]); + $this->create_manual_award(['recipientid' => $user1->id, 'issuerid' => $user2->id, 'badgeid' => $badge2->id]); + + $this->create_backpack(['userid' => $user2->id]); + $this->create_issued(['badgeid' => $badge2->id, 'userid' => $user3->id]); + + $crit = $this->create_criteria_manual($badge1->id); + $crit->mark_complete($user3->id); + + // The list of users for usercontext2 context should return users. + $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(2, $userlist1); + $this->assertTrue(in_array($user1->id, $userlist1->get_userids())); + $this->assertTrue(in_array($user2->id, $userlist1->get_userids())); + + // The list of users for coursecontext2 context should return users. + $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(2, $userlist2); + $this->assertTrue(in_array($user1->id, $userlist2->get_userids())); + $this->assertTrue(in_array($user3->id, $userlist2->get_userids())); + + // The list of users for system context should return users. + $systemcontext = context_system::instance(); + $userlist3 = new \core_privacy\local\request\userlist($systemcontext, $component); + provider::get_users_in_context($userlist3); + $this->assertCount(2, $userlist3); + $this->assertTrue(in_array($user1->id, $userlist3->get_userids())); + $this->assertTrue(in_array($user2->id, $userlist3->get_userids())); + + // Delete the data for user1 in usercontext2. + $approvedlist = new approved_userlist($usercontext2, $component, [$user1->id]); + // Delete using delete_data_for_user. No data for users in usercontext2 should be removed. + provider::delete_data_for_users($approvedlist); + // The list of users for usercontext2 context should still return user1, user2. + $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(2, $userlist1); + $this->assertTrue(in_array($user1->id, $userlist1->get_userids())); + $this->assertTrue(in_array($user2->id, $userlist1->get_userids())); + + // Delete the data for user2 in usercontext2. + $approvedlist = new approved_userlist($usercontext2, $component, [$user2->id]); + // Delete using delete_data_for_user. The user data in usercontext2 should be removed. + provider::delete_data_for_users($approvedlist); + // The list of users for usercontext2 context should not return any users. + $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(0, $userlist1); + + // The list of users for coursecontext2 context should return the previous users. + $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(2, $userlist2); + + // The list of users for system context should return the previous users. + $systemcontext = context_system::instance(); + $userlist3 = new \core_privacy\local\request\userlist($systemcontext, $component); + provider::get_users_in_context($userlist3); + $this->assertCount(2, $userlist3); + + // Make sure data is only deleted in the user context, nothing in course or system. + // Convert $userlist2 into an approved_contextlist. + $approvedlist = new approved_userlist($coursecontext2, $component, $userlist2->get_userids()); + provider::delete_data_for_users($approvedlist); + + // The list of users for coursecontext2 context should still return the user data. + $userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(2, $userlist2); + } + /** * Create a badge. *