Merge branch 'MDL-63689-master-workshopprivacy' of git://github.com/mudrd8mz/moodle

This commit is contained in:
Andrew Nicols 2018-11-05 14:54:09 +08:00
commit 19716edd9c
3 changed files with 335 additions and 1 deletions

View File

@ -27,10 +27,12 @@ namespace mod_workshop\privacy;
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\deletion_criteria;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
@ -45,6 +47,7 @@ require_once($CFG->dirroot.'/mod/workshop/locallib.php');
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\user_preference_provider,
\core_privacy\local\request\plugin\provider {
@ -161,6 +164,63 @@ class provider implements
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist To be filled list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
global $DB;
$context = $userlist->get_context();
if (!$context instanceof \context_module) {
return;
}
$params = [
'instanceid' => $context->instanceid,
'module' => 'workshop',
];
// One query to fetch them all, one query to find them, one query to bring them all and into the userlist add them.
$sql = "SELECT ws.authorid, ws.gradeoverby, wa.reviewerid, wa.gradinggradeoverby, wr.userid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
LEFT JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
LEFT JOIN {workshop_aggregations} wr ON wr.workshopid = w.id
WHERE cm.id = :instanceid";
$userids = [];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $r) {
if ($r->authorid) {
$userids[$r->authorid] = true;
}
if ($r->gradeoverby) {
$userids[$r->gradeoverby] = true;
}
if ($r->reviewerid) {
$userids[$r->reviewerid] = true;
}
if ($r->gradinggradeoverby) {
$userids[$r->gradinggradeoverby] = true;
}
if ($r->userid) {
$userids[$r->userid] = true;
}
}
$rs->close();
if ($userids) {
$userlist->add_users(array_keys($userids));
}
}
/**
* Export personal data stored in the given contexts.
*
@ -662,4 +722,119 @@ class provider implements
\core_plagiarism\privacy\provider::delete_plagiarism_for_user($user->id, $context);
}
}
/**
* Delete personal data for multiple users within a single workshop context.
*
* See documentation for {@link self::delete_data_for_user()} for more details on what we do and don't actually
* delete and why.
*
* @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();
$fs = get_file_storage();
if ($context->contextlevel != CONTEXT_MODULE) {
// This should not happen but let's be double sure when it comes to deleting data.
return;
}
$cm = get_coursemodule_from_id('workshop', $context->instanceid, 0, false, IGNORE_MISSING);
if (!$cm) {
// Probably some kind of expired context.
return;
}
$userids = $userlist->get_userids();
if (!$userids) {
return;
}
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
// Erase sensitive data in all submissions by all the users in the given context.
$sql = "SELECT ws.id AS submissionid
FROM {workshop} w
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
WHERE w.id = :workshopid AND ws.authorid $usersql";
$params = $userparams + [
'workshopid' => $cm->instance,
];
$submissionids = $DB->get_fieldset_sql($sql, $params);
if ($submissionids) {
list($submissionidsql, $submissionidparams) = $DB->get_in_or_equal($submissionids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_submissions', 'title', get_string('privacy:request:delete:title',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$DB->set_field_select('workshop_submissions', 'content', get_string('privacy:request:delete:content',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$DB->set_field_select('workshop_submissions', 'feedbackauthor', get_string('privacy:request:delete:content',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'submission_content',
$submissionidsql, $submissionidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'submission_attachment',
$submissionidsql, $submissionidparams);
}
// Erase personal data in received assessments - feedback is seen as belonging to the recipient.
$sql = "SELECT wa.id AS assessmentid
FROM {workshop} w
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE w.id = :workshopid AND ws.authorid $usersql";
$params = $userparams + [
'workshopid' => $cm->instance,
];
$assessmentids = $DB->get_fieldset_sql($sql, $params);
if ($assessmentids) {
list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_assessments', 'feedbackauthor', get_string('privacy:request:delete:content',
'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'overallfeedback_content',
$assessmentidsql, $assessmentidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'overallfeedback_attachment',
$assessmentidsql, $assessmentidparams);
}
// Erase sensitive data in provided assessments records.
$sql = "SELECT wa.id AS assessmentid
FROM {workshop} w
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE w.id = :workshopid AND wa.reviewerid $usersql";
$params = $userparams + [
'workshopid' => $cm->instance,
];
$assessmentids = $DB->get_fieldset_sql($sql, $params);
if ($assessmentids) {
list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_assessments', 'feedbackreviewer', get_string('privacy:request:delete:content',
'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
}
foreach ($userids as $userid) {
\core_plagiarism\privacy\provider::delete_plagiarism_for_user($userid, $context);
}
}
}

View File

@ -276,7 +276,7 @@ $string['privacy:metadata:workshopgrades'] = 'Holds information about how the as
$string['privacy:metadata:workshopid'] = 'ID of the workshop activity';
$string['privacy:metadata:workshopsubmissions'] = 'Holds information about workshop module submissions';
$string['privacy:request:delete:title'] = '[Deleted]';
$string['privacy:request:delete:content'] = 'The content has been deleted at the request of the user.';
$string['privacy:request:delete:content'] = 'The content has been deleted (requested by the user or expired)';
$string['publishedsubmissions'] = 'Published submissions';
$string['publishsubmission'] = 'Publish submission';
$string['publishsubmission_help'] = 'Published submissions are available to the others when the workshop is closed.';

View File

@ -204,6 +204,53 @@ class mod_workshop_privacy_provider_testcase extends advanced_testcase {
$this->assertEquals([$context21->id, $context12->id], $contextlist->get_contextids(), null, 0.0, 10, true);
}
/**
* Test {@link \mod_workshop\privacy\provider::get_users_in_context()} implementation.
*/
public function test_get_users_in_context() {
$cm11 = get_coursemodule_from_instance('workshop', $this->workshop11->id);
$cm12 = get_coursemodule_from_instance('workshop', $this->workshop12->id);
$cm21 = get_coursemodule_from_instance('workshop', $this->workshop21->id);
$context11 = context_module::instance($cm11->id);
$context12 = context_module::instance($cm12->id);
$context21 = context_module::instance($cm21->id);
// Users in the workshop11.
$userlist11 = new \core_privacy\local\request\userlist($context11, 'mod_workshop');
\mod_workshop\privacy\provider::get_users_in_context($userlist11);
$expected11 = [
$this->student1->id, // Student1 has data in workshop11 (author + self reviewer).
$this->student2->id, // Student2 has data in workshop11 (reviewer).
$this->student3->id, // Student3 has data in workshop11 (reviewer).
];
$actual11 = $userlist11->get_userids();
$this->assertEquals($expected11, $actual11, '', 0, 10, true);
// Users in the workshop12.
$userlist12 = new \core_privacy\local\request\userlist($context12, 'mod_workshop');
\mod_workshop\privacy\provider::get_users_in_context($userlist12);
$expected12 = [
$this->student1->id, // Student1 has data in workshop12 (author).
$this->student2->id, // Student2 has data in workshop12 (reviewer).
$this->teacher4->id, // Teacher4 has data in workshop12 (gradeoverby).
];
$actual12 = $userlist12->get_userids();
$this->assertEquals($expected12, $actual12, '', 0, 10, true);
// Users in the workshop21.
$userlist21 = new \core_privacy\local\request\userlist($context21, 'mod_workshop');
\mod_workshop\privacy\provider::get_users_in_context($userlist21);
$expected21 = [
$this->student1->id, // Student1 has data in workshop21 (reviewer).
$this->student2->id, // Student2 has data in workshop21 (author).
$this->teacher4->id, // Teacher4 has data in workshop21 (gradinggradeoverby).
];
$actual21 = $userlist21->get_userids();
$this->assertEquals($expected21, $actual21, '', 0, 10, true);
}
/**
* Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation.
*/
@ -376,4 +423,116 @@ class mod_workshop_privacy_provider_testcase extends advanced_testcase {
}
}
}
/**
* Test {@link \mod_workshop\privacy\provider::delete_data_for_users()} implementation.
*/
public function test_delete_data_for_users() {
global $DB;
// Student1 has submissions in two workshops.
$this->assertFalse($this->is_submission_erased($this->submission111));
$this->assertFalse($this->is_submission_erased($this->submission121));
// Student1 has self-assessed one their submission.
$this->assertFalse($this->is_given_assessment_erased($this->assessment1111));
$this->assertFalse($this->is_received_assessment_erased($this->assessment1111));
// Student2 and student3 peer-assessed student1's submission.
$this->assertFalse($this->is_given_assessment_erased($this->assessment1112));
$this->assertFalse($this->is_given_assessment_erased($this->assessment1113));
// Delete data owned by student1 and student3 in the workshop11.
$context11 = \context_module::instance($this->workshop11->cmid);
$approveduserlist = new \core_privacy\local\request\approved_userlist($context11, 'mod_workshop', [
$this->student1->id,
$this->student3->id,
]);
\mod_workshop\privacy\provider::delete_data_for_users($approveduserlist);
// Student1's submission is erased in workshop11 but not in the other workshop12.
$this->assertTrue($this->is_submission_erased($this->submission111));
$this->assertFalse($this->is_submission_erased($this->submission121));
// Student1's self-assessment is erased.
$this->assertTrue($this->is_given_assessment_erased($this->assessment1111));
$this->assertTrue($this->is_received_assessment_erased($this->assessment1111));
// Student1's received peer-assessments are also erased because they are "owned" by the recipient of the assessment.
$this->assertTrue($this->is_received_assessment_erased($this->assessment1112));
$this->assertTrue($this->is_received_assessment_erased($this->assessment1113));
// Student2's owned data in the given assessment are not erased.
$this->assertFalse($this->is_given_assessment_erased($this->assessment1112));
// Student3's owned data in the given assessment were erased because she/he was in the userlist.
$this->assertTrue($this->is_given_assessment_erased($this->assessment1113));
// Personal data in other contexts are not affected.
$this->assertFalse($this->is_submission_erased($this->submission121));
$this->assertFalse($this->is_given_assessment_erased($this->assessment2121));
$this->assertFalse($this->is_received_assessment_erased($this->assessment2121));
}
/**
* Check if the given submission has the author's personal data erased.
*
* @param int $submissionid Identifier of the submission.
* @return boolean
*/
protected function is_submission_erased(int $submissionid) {
global $DB;
$submission = $DB->get_record('workshop_submissions', ['id' => $submissionid], 'id, title, content', MUST_EXIST);
$titledeleted = $submission->title === get_string('privacy:request:delete:title', 'mod_workshop');
$contentdeleted = $submission->content === get_string('privacy:request:delete:content', 'mod_workshop');
if ($titledeleted && $contentdeleted) {
return true;
} else {
return false;
}
}
/**
* Check is the received assessment has recipient's (author's) personal data erased.
*
* @param int $assessmentid Identifier of the assessment.
* @return boolean
*/
protected function is_received_assessment_erased(int $assessmentid) {
global $DB;
$assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackauthor', MUST_EXIST);
if ($assessment->feedbackauthor === get_string('privacy:request:delete:content', 'mod_workshop')) {
return true;
} else {
return false;
}
}
/**
* Check is the given assessment has reviewer's personal data erased.
*
* @param int $assessmentid Identifier of the assessment.
* @return boolean
*/
protected function is_given_assessment_erased(int $assessmentid) {
global $DB;
$assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackreviewer', MUST_EXIST);
if ($assessment->feedbackreviewer === get_string('privacy:request:delete:content', 'mod_workshop')) {
return true;
} else {
return false;
}
}
}