Skip to content

Commit

Permalink
MDL-63711 core_grades: Delete data for specified users in a context.
Browse files Browse the repository at this point in the history
  • Loading branch information
abgreeve committed Nov 2, 2018
1 parent 8b019fb commit d5f8687
Show file tree
Hide file tree
Showing 2 changed files with 362 additions and 1 deletion.
135 changes: 134 additions & 1 deletion grade/classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
*/
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.
Expand Down Expand Up @@ -259,6 +260,101 @@ public static function get_contexts_for_userid(int $userid) : \core_privacy\loca
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.
*
Expand Down Expand Up @@ -595,6 +691,43 @@ public static function delete_data_for_user(approved_contextlist $contextlist) {
$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.
*
Expand Down
228 changes: 228 additions & 0 deletions grade/tests/privacy_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,160 @@ public function test_get_contexts_for_userid_grades_and_history() {
$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();
Expand Down Expand Up @@ -368,6 +522,80 @@ public function test_delete_data_for_user() {
$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();
Expand Down

0 comments on commit d5f8687

Please sign in to comment.