mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
Merge branch 'MDL-63689-master-workshopprivacy' of git://github.com/mudrd8mz/moodle
This commit is contained in:
commit
19716edd9c
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.';
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user