MDL-63711 core_grades: Delete data for specified users in a context.
authorAdrian Greeve <abgreeve@gmail.com>
Mon, 22 Oct 2018 06:37:48 +0000 (14:37 +0800)
committerAdrian Greeve <abgreeve@gmail.com>
Fri, 2 Nov 2018 18:26:24 +0000 (02:26 +0800)
grade/classes/privacy/provider.php
grade/tests/privacy_test.php

index 39ec59c..b5ea2e4 100644 (file)
@@ -50,7 +50,8 @@ require_once($CFG->libdir . '/gradelib.php');
  */
 class provider implements
     \core_privacy\local\metadata\provider,
-    \core_privacy\local\request\subsystem\provider {
+    \core_privacy\local\request\subsystem\provider,
+    \core_privacy\local\request\core_userlist_provider {
 
     /**
      * Returns metadata.
@@ -259,6 +260,101 @@ class provider implements
         return $contextlist;
     }
 
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param   \core_privacy\local\request\userlist    $userlist   The userlist containing the list of users who have data
+     * in this context/plugin combination.
+     */
+    public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
+        $context = $userlist->get_context();
+
+        if ($context->contextlevel == CONTEXT_COURSE) {
+            $params = ['contextinstanceid' => $context->instanceid];
+
+            $sql = "SELECT usermodified
+                      FROM {grade_outcomes}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('usermodified', $sql, $params);
+
+            $sql = "SELECT loggeduser
+                      FROM {grade_outcomes_history}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('loggeduser', $sql, $params);
+
+            $sql = "SELECT userid
+                      FROM {scale}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('userid', $sql, $params);
+
+            $sql = "SELECT loggeduser, userid
+                      FROM {scale_history}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('loggeduser', $sql, $params);
+            $userlist->add_from_sql('userid', $sql, $params);
+
+            $sql = "SELECT loggeduser
+                      FROM {grade_items_history}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('loggeduser', $sql, $params);
+
+            $sql = "SELECT ggh.userid
+                      FROM {grade_grades_history} ggh
+                      JOIN {grade_items} gi ON ggh.itemid = gi.id
+                     WHERE gi.courseid = :contextinstanceid";
+            $userlist->add_from_sql('userid', $sql, $params);
+
+            $sql = "SELECT gg.userid, gg.usermodified
+                      FROM {grade_grades} gg
+                      JOIN {grade_items} gi ON gg.itemid = gi.id
+                     WHERE gi.courseid = :contextinstanceid";
+            $userlist->add_from_sql('userid', $sql, $params);
+            $userlist->add_from_sql('usermodified', $sql, $params);
+
+            $sql = "SELECT loggeduser
+                      FROM {grade_categories_history}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('loggeduser', $sql, $params);
+        }
+
+        // None of these are currently used (user deletion).
+        if ($context->contextlevel == CONTEXT_SYSTEM) {
+            $params = ['contextinstanceid' => 0];
+
+            $sql = "SELECT usermodified
+                      FROM {grade_outcomes}
+                     WHERE (courseid IS NULL OR courseid < 1)";
+            $userlist->add_from_sql('usermodified', $sql, []);
+
+            $sql = "SELECT loggeduser
+                      FROM {grade_outcomes_history}
+                     WHERE (courseid IS NULL OR courseid < 1)";
+            $userlist->add_from_sql('loggeduser', $sql, []);
+
+            $sql = "SELECT userid
+                      FROM {scale}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('userid', $sql, $params);
+
+            $sql = "SELECT loggeduser, userid
+                      FROM {scale_history}
+                     WHERE courseid = :contextinstanceid";
+            $userlist->add_from_sql('loggeduser', $sql, $params);
+            $userlist->add_from_sql('userid', $sql, $params);
+        }
+
+        if ($context->contextlevel == CONTEXT_USER) {
+            // If the grade item has been removed and we have an orphan entry then we link to the
+            // user context.
+            $sql = "SELECT ggh.userid
+                      FROM {grade_grades_history} ggh
+                 LEFT JOIN {grade_items} gi ON ggh.itemid = gi.id
+                     WHERE gi.id IS NULL
+                       AND ggh.userid = :contextinstanceid";
+            $userlist->add_from_sql('userid', $sql, ['contextinstanceid' => $context->instanceid]);
+        }
+    }
+
     /**
      * Export all user data for the specified user, in the specified contexts.
      *
@@ -595,6 +691,43 @@ class provider implements
         $DB->delete_records_select('grade_grades_history', "itemid $insql AND userid = :userid", $params);
     }
 
+
+    /**
+     * Delete multiple users within a single context.
+     *
+     * @param   \core_privacy\local\request\approved_userlist $userlist The approved context and user information to
+     * delete information for.
+     */
+    public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
+        global $DB;
+
+        $context = $userlist->get_context();
+        $userids = $userlist->get_userids();
+        if ($context->contextlevel == CONTEXT_USER) {
+            if (array_search($context->instanceid, $userids) !== false) {
+                static::delete_orphan_historical_grades($context->instanceid);
+            }
+            return;
+        }
+
+        if ($context->contextlevel != CONTEXT_COURSE) {
+            return;
+        }
+
+        $itemids = static::get_item_ids_from_course_ids([$context->instanceid]);
+        if (empty($itemids)) {
+            // Our job here is done!
+            return;
+        }
+
+        // Delete all the grades.
+        list($itemsql, $itemparams) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
+        list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+        $params = array_merge($itemparams, $userparams);
+        $DB->delete_records_select('grade_grades', "itemid $itemsql AND userid $usersql", $params);
+        $DB->delete_records_select('grade_grades_history', "itemid $itemsql AND userid $usersql", $params);
+    }
+
     /**
      * Delete orphan historical grades.
      *
index f7dfc15..b4d6009 100644 (file)
@@ -248,6 +248,160 @@ class core_grades_privacy_testcase extends provider_testcase {
         $this->assertArrayHasKey(context_user::instance($u2->id)->id, $contexts);
     }
 
+    /**
+     * Test that the appropriate user IDs are returned for a given context.
+     */
+    public function test_get_users_in_context_gradebook_edits() {
+        $dg = $this->getDataGenerator();
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $u5 = $dg->create_user();
+        $u6 = $dg->create_user();
+        $u7 = $dg->create_user();
+        $u8 = $dg->create_user();
+        $u9 = $dg->create_user();
+        $u10 = $dg->create_user();
+        $u11 = $dg->create_user();
+
+        $sysctx = context_system::instance();
+        $c1ctx = context_course::instance($c1->id);
+        $c2ctx = context_course::instance($c2->id);
+
+        // Create some stuff.
+        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
+        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
+        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
+        $gc1a = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
+        $gc1b = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
+        $gc2a = new grade_category($dg->create_grade_category(['courseid' => $c2->id]), false);
+        $go2 = new grade_outcome($dg->create_grade_outcome(['courseid' => $c2->id, 'shortname' => 'go2',
+            'fullname' => 'go2']), false);
+
+        $go0 = new grade_outcome(['shortname' => 'go0', 'fullname' => 'go0', 'usermodified' => $u1->id]);
+        $go0->insert();
+        $go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u11->id]);
+        $go1->insert();
+
+        // Create scales.
+        $s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']);
+        $s1->insert();
+        $s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']);
+        $s2->insert();
+
+        // User 2 creates history.
+        $this->setUser($u2);
+        $go0->shortname .= ' edited';
+        $go0->update();
+        $gc1a->fullname .= ' edited';
+        $gc1a->update();
+
+        // User 3 creates history.
+        $this->setUser($u3);
+        $go1->shortname .= ' edited';
+        $go1->update();
+        $gc2a->fullname .= ' a';
+        $gc2a->update();
+
+        // User 4 updates an outcome in course (creates history).
+        $this->setUser($u4);
+        $go2->shortname .= ' edited';
+        $go2->update();
+
+        // User 5 updates an item.
+        $this->setUser($u5);
+        $gi1a->itemname .= ' edited';
+        $gi1a->update();
+
+        // User 6 creates history.
+        $this->setUser($u6);
+        $gi2a->delete();
+
+        // User 9 creates history.
+        $this->setUser($u9);
+        $s1->name .= ' edited';
+        $s1->update();
+
+        $userlist = new \core_privacy\local\request\userlist($sysctx, 'core_grades');
+        provider::get_users_in_context($userlist);
+        $systemcontextuserids = [$u1->id, $u2->id, $u7->id, $u9->id];
+        $this->assertEquals($systemcontextuserids, $userlist->get_userids());
+
+        $userlist = new \core_privacy\local\request\userlist($c1ctx, 'core_grades');
+        provider::get_users_in_context($userlist);
+        $course1userids = [$u11->id, $u3->id, $u8->id, $u5->id, $u2->id];
+        $this->assertEquals($course1userids, $userlist->get_userids());
+
+        $userlist = new \core_privacy\local\request\userlist($c2ctx, 'core_grades');
+        provider::get_users_in_context($userlist);
+        $course2userids = [$u4->id, $u6->id, $u3->id];
+        $this->assertEquals($course2userids, $userlist->get_userids());
+    }
+
+    /**
+     * Test that the appropriate user IDs are returned for a given context.
+     */
+    public function test_get_users_in_context_grades_and_history() {
+        $dg = $this->getDataGenerator();
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $u5 = $dg->create_user();
+        $u6 = $dg->create_user();
+
+        $c1ctx = context_course::instance($c1->id);
+        $c2ctx = context_course::instance($c2->id);
+        $u2ctx = context_user::instance($u2->id);
+
+        // Create some stuff.
+        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
+        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
+        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
+        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
+
+        // User 1 is graded in course 1.
+        $gi1a->update_final_grade($u1->id, 1, 'test');
+
+        // User 2 is graded in course 2.
+        $gi2a->update_final_grade($u2->id, 10, 'test');
+
+        // User 3 is set as modifier.
+        $gi1a->update_final_grade($u1->id, 1, 'test', '', FORMAT_MOODLE, $u3->id);
+
+        // User 4 is set as modifier, and creates history..
+        $this->setUser($u4);
+        $gi1a->update_final_grade($u2->id, 1, 'test');
+
+        // User 5 creates history, user 6 is the known modifier, and we delete the item.
+        $this->setUser($u5);
+        $gi2b->update_final_grade($u2->id, 1, 'test', '', FORMAT_PLAIN, $u6->id);
+        $gi2b->delete();
+
+        $userlist = new \core_privacy\local\request\userlist($c1ctx, 'core_grades');
+        provider::get_users_in_context($userlist);
+        $course1userids = [$u1->id, $u2->id, $u3->id, $u4->id];
+        $this->assertEquals($course1userids, $userlist->get_userids());
+
+        $userlist = new \core_privacy\local\request\userlist($c2ctx, 'core_grades');
+        provider::get_users_in_context($userlist);
+        $course2userids = [$u5->id, $u2->id];
+        $this->assertEquals($course2userids, $userlist->get_userids());
+
+        $userlist = new \core_privacy\local\request\userlist($u2ctx, 'core_grades');
+        provider::get_users_in_context($userlist);
+        $this->assertEquals([$u2->id], $userlist->get_userids());
+    }
+
     public function test_delete_data_for_all_users_in_context() {
         global $DB;
         $dg = $this->getDataGenerator();
@@ -368,6 +522,80 @@ class core_grades_privacy_testcase extends provider_testcase {
         $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
     }
 
+    /**
+     * Test deleting multiple users for a context works.
+     */
+    public function test_delete_data_for_users() {
+        global $DB;
+        $dg = $this->getDataGenerator();
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+        $c1ctx = context_course::instance($c1->id);
+        $c2ctx = context_course::instance($c2->id);
+
+        // Create some stuff.
+        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
+        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
+        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
+        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
+
+        $gi1a->update_final_grade($u1->id, 1, 'test');
+        $gi1a->update_final_grade($u2->id, 1, 'test');
+        $gi1a->update_final_grade($u3->id, 1, 'test');
+        $gi1b->update_final_grade($u1->id, 1, 'test');
+        $gi1b->update_final_grade($u4->id, 1, 'test');
+        $gi2a->update_final_grade($u1->id, 1, 'test');
+        $gi2a->update_final_grade($u2->id, 1, 'test');
+        $gi2a->update_final_grade($u4->id, 1, 'test');
+        $gi2b->update_final_grade($u1->id, 1, 'test');
+        $gi2b->update_final_grade($u2->id, 1, 'test');
+        $gi2b->update_final_grade($u3->id, 1, 'test');
+        $gi2b->delete();
+
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi1a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi1b->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi2a->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi2b->id]));
+
+        $userlist = new \core_privacy\local\request\approved_userlist($c1ctx, 'core_grades', [$u1->id, $u2->id]);
+        provider::delete_data_for_users($userlist);
+
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi1a->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi1b->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi2a->id]));
+
+        $userlist = new \core_privacy\local\request\approved_userlist($c2ctx, 'core_grades', [$u2->id, $u4->id]);
+        provider::delete_data_for_users($userlist);
+
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi1a->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi1b->id]));
+        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
+        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi2a->id]));
+    }
+
     public function test_export_data_for_user_about_grades_and_history() {
         global $DB;
         $dg = $this->getDataGenerator();