Skip to content

Commit

Permalink
MDL-63497 mod_feedback: Add support for removal of context users
Browse files Browse the repository at this point in the history
This issue is a part of the MDL-62560 Epic.
  • Loading branch information
mickhawkins authored and David Monllao committed Oct 22, 2018
1 parent ebbf35d commit c79943c
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 0 deletions.
77 changes: 77 additions & 0 deletions mod/feedback/classes/privacy/provider.php
Expand Up @@ -31,9 +31,11 @@
use stdClass;
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\contextlist;
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/feedback/lib.php');
Expand All @@ -48,6 +50,7 @@
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {

/**
Expand Down Expand Up @@ -102,6 +105,38 @@ public static function get_contexts_for_userid(int $userid) : contextlist {
return $contextlist;
}

/**
* 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;
}

// Find users with feedback entries.
$sql = "
SELECT fc.userid
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
AND ctx.contextlevel = :modlevel
WHERE ctx.id = :contextid";
$params = ['feedback' => 'feedback', 'modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];

$userlist->add_from_sql('userid', sprintf($sql, 'feedback_completed'), $params);
$userlist->add_from_sql('userid', sprintf($sql, 'feedback_completedtmp'), $params);
}

/**
* Export all user data for the specified user, in the specified contexts.
*
Expand Down Expand Up @@ -272,6 +307,48 @@ public static function delete_data_for_user(approved_contextlist $contextlist) {
}
}

/**
* 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();
$userids = $userlist->get_userids();

// Prepare SQL to gather all completed IDs.
list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$completedsql = "
SELECT fc.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
WHERE cm.id = :instanceid
AND fc.userid $insql";
$completedparams = array_merge($inparams, ['instanceid' => $context->instanceid, 'feedback' => 'feedback']);

// Delete all submissions in progress.
$completedtmpids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completedtmp'), $completedparams);
if (!empty($completedtmpids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedtmpids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_valuetmp', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completedtmp', "id $insql", $inparams);
}

// Delete all final submissions.
$completedids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completed'), $completedparams);
if (!empty($completedids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_value', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completed', "id $insql", $inparams);
}
}

/**
* Extract an item record from a database record.
*
Expand Down
137 changes: 137 additions & 0 deletions mod/feedback/tests/privacy_test.php
Expand Up @@ -115,6 +115,83 @@ public function test_get_contexts_for_userid() {
$this->assertTrue(in_array(context_module::instance($cm2c->cmid)->id, $contextids));
}

/**
* Test getting the users in a context.
*/
public function test_get_users_in_context() {
global $DB;
$dg = $this->getDataGenerator();
$fg = $dg->get_plugin_generator('mod_feedback');
$component = 'mod_feedback';

$c1 = $dg->create_course();
$c2 = $dg->create_course();
$cm0 = $dg->create_module('feedback', ['course' => SITEID]);
$cm1a = $dg->create_module('feedback', ['course' => $c1, 'anonymous' => FEEDBACK_ANONYMOUS_NO]);
$cm1b = $dg->create_module('feedback', ['course' => $c1]);
$cm2 = $dg->create_module('feedback', ['course' => $c2]);

$u1 = $dg->create_user();
$u2 = $dg->create_user();

foreach ([$cm0, $cm1a, $cm1b, $cm2] as $feedback) {
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];

if ($feedback == $cm1b) {
$this->create_submission_with_answers($feedback, $u2, $answers);
} else {
$this->create_submission_with_answers($feedback, $u1, $answers);
}
}

// Unsaved submission for u2 in cm1a.
$feedback = $cm1a;
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];
$this->create_tmp_submission_with_answers($feedback, $u2, $answers);

// Only u1 in cm0.
$context = context_module::instance($cm0->cmid);
$userlist = new \core_privacy\local\request\userlist($context, $component);
provider::get_users_in_context($userlist);

$this->assertCount(1, $userlist);
$this->assertEquals([$u1->id], $userlist->get_userids());

$context = context_module::instance($cm1a->cmid);
$userlist = new \core_privacy\local\request\userlist($context, $component);
provider::get_users_in_context($userlist);

// Two submissions in cm1a: saved for u1, unsaved for u2.
$this->assertCount(2, $userlist);

$expected = [$u1->id, $u2->id];
$actual = $userlist->get_userids();
sort($expected);
sort($actual);

$this->assertEquals($expected, $actual);

// Only u2 in cm1b.
$context = context_module::instance($cm1b->cmid);
$userlist = new \core_privacy\local\request\userlist($context, $component);
provider::get_users_in_context($userlist);

$this->assertCount(1, $userlist);
$this->assertEquals([$u2->id], $userlist->get_userids());

// Only u1 in cm2.
$context = context_module::instance($cm2->cmid);
$userlist = new \core_privacy\local\request\userlist($context, $component);
provider::get_users_in_context($userlist);

$this->assertCount(1, $userlist);
$this->assertEquals([$u1->id], $userlist->get_userids());
}

/**
* Test deleting user data.
*/
Expand Down Expand Up @@ -169,6 +246,66 @@ public function test_delete_data_for_user() {

}

/**
* Test deleting data within a context for an approved userlist.
*/
public function test_delete_data_for_users() {
global $DB;
$dg = $this->getDataGenerator();
$fg = $dg->get_plugin_generator('mod_feedback');

$c1 = $dg->create_course();
$c2 = $dg->create_course();
$cm0 = $dg->create_module('feedback', ['course' => SITEID]);
$cm1 = $dg->create_module('feedback', ['course' => $c1, 'anonymous' => FEEDBACK_ANONYMOUS_NO]);
$cm2 = $dg->create_module('feedback', ['course' => $c2]);
$context0 = context_module::instance($cm0->cmid);
$context1 = context_module::instance($cm1->cmid);

$u1 = $dg->create_user();
$u2 = $dg->create_user();

// Create a bunch of data.
foreach ([$cm0, $cm1, $cm2] as $feedback) {
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];

$this->create_submission_with_answers($feedback, $u1, $answers);
$this->create_tmp_submission_with_answers($feedback, $u1, $answers);

$this->create_submission_with_answers($feedback, $u2, $answers);
$this->create_tmp_submission_with_answers($feedback, $u2, $answers);
}

// Delete u1 from cm0, ensure u2 data is retained.
$approveduserlist = new core_privacy\local\request\approved_userlist($context0, 'mod_feedback', [$u1->id]);
provider::delete_data_for_users($approveduserlist);

$this->assert_no_feedback_data_for_user($cm0, $u1);
$this->assert_feedback_data_for_user($cm0, $u2);
$this->assert_feedback_tmp_data_for_user($cm0, $u2);

// Ensure cm1 unaffected by cm1 deletes.
$this->assert_feedback_data_for_user($cm1, $u1);
$this->assert_feedback_tmp_data_for_user($cm1, $u1);
$this->assert_feedback_data_for_user($cm1, $u2);
$this->assert_feedback_tmp_data_for_user($cm1, $u2);

// Delete u1 and u2 from cm1, ensure no data is retained.
$approveduserlist = new core_privacy\local\request\approved_userlist($context1, 'mod_feedback', [$u1->id, $u2->id]);
provider::delete_data_for_users($approveduserlist);

$this->assert_no_feedback_data_for_user($cm1, $u1);
$this->assert_no_feedback_data_for_user($cm1, $u2);

// Ensure cm2 is unaffected by any of the deletes.
$this->assert_feedback_data_for_user($cm2, $u1);
$this->assert_feedback_tmp_data_for_user($cm2, $u1);
$this->assert_feedback_data_for_user($cm2, $u2);
$this->assert_feedback_tmp_data_for_user($cm2, $u2);
}

/**
* Test deleting a whole context.
*/
Expand Down

0 comments on commit c79943c

Please sign in to comment.