MDL-61905 workshop: Implement the privacy API in the workshop core

Workshop module stores personal data in its tables, via user
preference and via core_files and core_plagiarism subsystems.

When exporting the data, we export not only data created by users
themselves (such as their submissions and provided peer-assessments) but
also all relevant data that can (or must) be used to interpret created
content and evaluate the user's performance and skills.

On the other hand, when deleting data at user's request, we delete only
those data that do not affect other users' performance evaluation. The
reasoning is that one's right for privacy does not overweight someone
else's right for fair assessment. For that reason, we can't fully delete
whole provided peer-assessments, for example. Because they are used in
cross-comparison and grading evaluation of all other peers who assessed
the same submission. So instead, we replace provided texts but still
keep the original record.

Workshop defines the interface for its grading strategy subplugins to
allow them attach personal data under their control to the exported
structures.
This commit is contained in:
David Mudrák
2018-05-09 10:03:31 +02:00
parent 4f7b3cb360
commit d3ae85a539
6 changed files with 1196 additions and 0 deletions
+668
View File
@@ -0,0 +1,668 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines {@link \mod_workshop\privacy\provider} class.
*
* @package mod_workshop
* @category privacy
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
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\writer;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/mod/workshop/locallib.php');
/**
* Privacy API implementation for the Workshop activity module.
*
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\user_preference_provider,
\core_privacy\local\request\plugin\provider {
/**
* Describe all the places where the Workshop module stores some personal data.
*
* @param collection $collection Collection of items to add metadata to.
* @return collection Collection with our added items.
*/
public static function get_metadata(collection $collection) {
$collection->add_database_table('workshop_submissions', [
'workshopid' => 'privacy:metadata:workshopid',
'authorid' => 'privacy:metadata:authorid',
'example' => 'privacy:metadata:example',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
'title' => 'privacy:metadata:submissiontitle',
'content' => 'privacy:metadata:submissioncontent',
'contentformat' => 'privacy:metadata:submissioncontentformat',
'grade' => 'privacy:metadata:submissiongrade',
'gradeover' => 'privacy:metadata:submissiongradeover',
'feedbackauthor' => 'privacy:metadata:feedbackauthor',
'feedbackauthorformat' => 'privacy:metadata:feedbackauthorformat',
'published' => 'privacy:metadata:published',
'late' => 'privacy:metadata:late',
], 'privacy:metadata:workshopsubmissions');
$collection->add_database_table('workshop_assessments', [
'submissionid' => 'privacy:metadata:submissionid',
'reviewerid' => 'privacy:metadata:reviewerid',
'weight' => 'privacy:metadata:weight',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
'grade' => 'privacy:metadata:assessmentgrade',
'gradinggrade' => 'privacy:metadata:assessmentgradinggrade',
'gradinggradeover' => 'privacy:metadata:assessmentgradinggradeover',
'feedbackauthor' => 'privacy:metadata:feedbackauthor',
'feedbackauthorformat' => 'privacy:metadata:feedbackauthorformat',
'feedbackreviewer' => 'privacy:metadata:feedbackreviewer',
'feedbackreviewerformat' => 'privacy:metadata:feedbackreviewerformat',
], 'privacy:metadata:workshopassessments');
$collection->add_database_table('workshop_grades', [
'assessmentid' => 'privacy:metadata:assessmentid',
'strategy' => 'privacy:metadata:strategy',
'dimensionid' => 'privacy:metadata:dimensionid',
'grade' => 'privacy:metadata:dimensiongrade',
'peercomment' => 'privacy:metadata:peercomment',
'peercommentformat' => 'privacy:metadata:peercommentformat',
], 'privacy:metadata:workshopgrades');
$collection->add_database_table('workshop_aggregations', [
'workshopid' => 'privacy:metadata:workshopid',
'userid' => 'privacy:metadata:userid',
'gradinggrade' => 'privacy:metadata:aggregatedgradinggrade',
'timegraded' => 'privacy:metadata:timeaggregated',
], 'privacy:metadata:workshopaggregations');
$collection->add_subsystem_link('core_files', [], 'privacy:metadata:subsystem:corefiles');
$collection->add_subsystem_link('core_plagiarism', [], 'privacy:metadata:subsystem:coreplagiarism');
$collection->add_user_preference('workshop_perpage', 'privacy:metadata:preference:perpage');
return $collection;
}
/**
* Get the list of contexts that contain personal data for the specified user.
*
* User has personal data in the workshop if any of the following cases happens:
*
* - the user has submitted in the workshop
* - the user has overridden a submission grade
* - the user has been assigned as a reviewer of a submission
* - the user has overridden a grading grade
* - the user has a grading grade (existing or to be calculated)
*
* @param int $userid ID of the user.
* @return contextlist List of contexts containing the user's personal data.
*/
public static function get_contexts_for_userid($userid) {
$contextlist = new contextlist();
$sql = "SELECT ctx.id
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
JOIN {workshop} w ON cm.instance = w.id
LEFT 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 ws.authorid = :wsauthorid
OR ws.gradeoverby = :wsgradeoverby
OR wa.reviewerid = :wareviewerid
OR wa.gradinggradeoverby = :wagradinggradeoverby
OR wr.userid = :wruserid";
$params = [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'wsauthorid' => $userid,
'wsgradeoverby' => $userid,
'wareviewerid' => $userid,
'wagradinggradeoverby' => $userid,
'wruserid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Export personal data stored in the given contexts.
*
* @param approved_contextlist $contextlist List of contexts approved for export.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
if (!count($contextlist)) {
return;
}
$user = $contextlist->get_user();
// Export general information about all workshops.
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel != CONTEXT_MODULE) {
continue;
}
$data = helper::get_context_data($context, $user);
static::append_extra_workshop_data($context, $user, $data, []);
writer::with_context($context)->export_data([], $data);
helper::export_context_files($context, $user);
}
// Export the user's own submission and all example submissions he/she created.
static::export_submissions($contextlist);
// Export all given assessments.
static::export_assessments($contextlist);
}
/**
* Export user preferences controlled by this plugin.
*
* @param int $userid ID of the user we are exporting data for
*/
public static function export_user_preferences($userid) {
$perpage = get_user_preferences('workshop_perpage', null, $userid);
if ($perpage !== null) {
writer::export_user_preference('mod_workshop', 'workshop_perpage', $perpage,
get_string('privacy:metadata:preference:perpage', 'mod_workshop'));
}
}
/**
* Append additional relevant data into the base data about the workshop instance.
*
* Relevant are data that are important for interpreting or evaluating the performance of the user expressed in
* his/her exported personal data. For example, we need to know what were the instructions for submissions or what
* was the phase of the workshop when it was exported.
*
* @param context $context Workshop module content.
* @param stdClass $user User for which we are exporting data.
* @param stdClass $data Base data about the workshop instance to append to.
* @param array $subcontext Subcontext path items to eventually write files into.
*/
protected static function append_extra_workshop_data(\context $context, \stdClass $user, \stdClass $data, array $subcontext) {
global $DB;
if ($context->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Unexpected context provided');
}
$sql = "SELECT w.instructauthors, w.instructauthorsformat, w.instructreviewers, w.instructreviewersformat, w.phase,
w.strategy, w.evaluation, w.latesubmissions, w.submissionstart, w.submissionend, w.assessmentstart,
w.assessmentend, w.conclusion, w.conclusionformat
FROM {course_modules} cm
JOIN {workshop} w ON cm.instance = w.id
WHERE cm.id = :cmid";
$params = [
'cmid' => $context->instanceid,
];
$record = $DB->get_record_sql($sql, $params, MUST_EXIST);
$writer = writer::with_context($context);
if ($record->phase >= \workshop::PHASE_SUBMISSION) {
$data->instructauthors = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'instructauthors', 0,
$record->instructauthors);
$data->instructauthorsformat = $record->instructauthorsformat;
}
if ($record->phase >= \workshop::PHASE_ASSESSMENT) {
$data->instructreviewers = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'instructreviewers', 0,
$record->instructreviewers);
$data->instructreviewersformat = $record->instructreviewersformat;
}
if ($record->phase >= \workshop::PHASE_CLOSED) {
$data->conclusion = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'conclusion', 0, $record->conclusion);
$data->conclusionformat = $record->conclusionformat;
}
$data->strategy = \workshop::available_strategies_list()[$record->strategy];
$data->evaluation = \workshop::available_evaluators_list()[$record->evaluation];
$data->latesubmissions = transform::yesno($record->latesubmissions);
$data->submissionstart = $record->submissionstart ? transform::datetime($record->submissionstart) : null;
$data->submissionend = $record->submissionend ? transform::datetime($record->submissionend) : null;
$data->assessmentstart = $record->assessmentstart ? transform::datetime($record->assessmentstart) : null;
$data->assessmentend = $record->assessmentend ? transform::datetime($record->assessmentend) : null;
switch ($record->phase) {
case \workshop::PHASE_SETUP:
$data->phase = get_string('phasesetup', 'mod_workshop');
break;
case \workshop::PHASE_SUBMISSION:
$data->phase = get_string('phasesubmission', 'mod_workshop');
break;
case \workshop::PHASE_ASSESSMENT:
$data->phase = get_string('phaseassessment', 'mod_workshop');
break;
case \workshop::PHASE_EVALUATION:
$data->phase = get_string('phaseevaluation', 'mod_workshop');
break;
case \workshop::PHASE_CLOSED:
$data->phase = get_string('phaseclosed', 'mod_workshop');
break;
}
$writer->export_area_files($subcontext, 'mod_workshop', 'instructauthors', 0);
$writer->export_area_files($subcontext, 'mod_workshop', 'instructreviewers', 0);
$writer->export_area_files($subcontext, 'mod_workshop', 'conclusion', 0);
}
/**
* Export all user's submissions and example submissions he/she created in the given contexts.
*
* @param approved_contextlist $contextlist List of contexts approved for export.
*/
protected static function export_submissions(approved_contextlist $contextlist) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$user = $contextlist->get_user();
$sql = "SELECT ws.id, ws.authorid, ws.example, ws.timecreated, ws.timemodified, ws.title,
ws.content, ws.contentformat, ws.grade, ws.gradeover, ws.feedbackauthor, ws.feedbackauthorformat,
ws.published, ws.late,
w.phase, w.course, cm.id AS cmid, ".\context_helper::get_preload_record_columns_sql('ctx')."
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
WHERE ctx.id {$contextsql}
AND ws.authorid = :authorid";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'authorid' => $user->id,
];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $record) {
\context_helper::preload_from_record($record);
$context = \context_module::instance($record->cmid);
$writer = \core_privacy\local\request\writer::with_context($context);
if ($record->example) {
$subcontext = [get_string('examplesubmissions', 'mod_workshop'), $record->id];
$mysubmission = null;
} else {
$subcontext = [get_string('mysubmission', 'mod_workshop')];
$mysubmission = $record;
}
$phase = $record->phase;
$courseid = $record->course;
$data = (object) [
'example' => transform::yesno($record->example),
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
'title' => $record->title,
'content' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
'submission_content', $record->id, $record->content),
'contentformat' => $record->contentformat,
'grade' => $record->grade,
'gradeover' => $record->gradeover,
'feedbackauthor' => $record->feedbackauthor,
'feedbackauthorformat' => $record->feedbackauthorformat,
'published' => transform::yesno($record->published),
'late' => transform::yesno($record->late),
];
$writer->export_data($subcontext, $data);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_content', $record->id);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_attachment', $record->id);
// Export peer-assessments of my submission if the workshop was closed. We do not export received
// assessments from peers before they were actually effective. Before the workshop is closed, grades are not
// pushed into the gradebook. So peer assessments did not affect evaluation of the user's performance and
// they should not be considered as their personal data. This is different from assessments given by the
// user that are always exported.
if ($mysubmission && $phase == \workshop::PHASE_CLOSED) {
$assessments = $DB->get_records('workshop_assessments', ['submissionid' => $mysubmission->id], '',
'id, reviewerid, weight, timecreated, timemodified, grade, feedbackauthor, feedbackauthorformat');
foreach ($assessments as $assessment) {
$assid = $assessment->id;
$assessment->selfassessment = transform::yesno($assessment->reviewerid == $user->id);
$assessment->timecreated = transform::datetime($assessment->timecreated);
$assessment->timemodified = $assessment->timemodified ? transform::datetime($assessment->timemodified) : null;
$assessment->feedbackauthor = $writer->rewrite_pluginfile_urls($subcontext,
'mod_workshop', 'overallfeedback_content', $assid, $assessment->feedbackauthor);
$assessmentsubcontext = array_merge($subcontext, [get_string('assessments', 'mod_workshop'), $assid]);
unset($assessment->id);
unset($assessment->reviewerid);
$writer->export_data($assessmentsubcontext, $assessment);
$writer->export_area_files($assessmentsubcontext, 'mod_workshop', 'overallfeedback_content', $assid);
$writer->export_area_files($assessmentsubcontext, 'mod_workshop', 'overallfeedback_attachment', $assid);
// Export details of how the assessment forms were filled.
static::export_assessment_forms($user, $context, $assessmentsubcontext, $assid);
}
}
// Export plagiarism data related to the submission content.
// The last $linkarray argument consistent with how we call {@link plagiarism_get_links()} in the renderer.
\core_plagiarism\privacy\provider::export_plagiarism_user_data($user->id, $context, $subcontext, [
'userid' => $user->id,
'content' => format_text($data->content, $data->contentformat, ['overflowdiv' => true]),
'cmid' => $context->instanceid,
'course' => $courseid,
]);
}
$rs->close();
}
/**
* Export all assessments given by the user.
*
* @param approved_contextlist $contextlist List of contexts approved for export.
*/
protected static function export_assessments(approved_contextlist $contextlist) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$user = $contextlist->get_user();
$sql = "SELECT ws.authorid, ws.example, ws.timecreated, ws.timemodified, ws.title, ws.content, ws.contentformat,
wa.id, wa.submissionid, wa.reviewerid, wa.weight, wa.timecreated, wa.timemodified, wa.grade,
wa.gradinggrade, wa.gradinggradeover, wa.feedbackauthor, wa.feedbackauthorformat, wa.feedbackreviewer,
wa.feedbackreviewerformat, cm.id AS cmid, ".\context_helper::get_preload_record_columns_sql('ctx')."
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE ctx.id {$contextsql}
AND wa.reviewerid = :reviewerid";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'reviewerid' => $user->id,
];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $record) {
\context_helper::preload_from_record($record);
$context = \context_module::instance($record->cmid);
$writer = \core_privacy\local\request\writer::with_context($context);
$subcontext = [get_string('myassessments', 'mod_workshop'), $record->id];
$data = (object) [
'weight' => $record->weight,
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
'grade' => $record->grade,
'gradinggrade' => $record->gradinggrade,
'gradinggradeover' => $record->gradinggradeover,
'feedbackauthor' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
'overallfeedback_content', $record->id, $record->feedbackauthor),
'feedbackauthorformat' => $record->feedbackauthorformat,
'feedbackreviewer' => $record->feedbackreviewer,
'feedbackreviewerformat' => $record->feedbackreviewerformat,
];
$submission = (object) [
'myownsubmission' => transform::yesno($record->authorid == $user->id),
'example' => transform::yesno($record->example),
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
'title' => $record->title,
'content' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
'submission_content', $record->submissionid, $record->content),
'contentformat' => $record->contentformat,
];
$writer->export_data($subcontext, $data);
$writer->export_related_data($subcontext, 'submission', $submission);
$writer->export_area_files($subcontext, 'mod_workshop', 'overallfeedback_content', $record->id);
$writer->export_area_files($subcontext, 'mod_workshop', 'overallfeedback_attachment', $record->id);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_content', $record->submissionid);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_attachment', $record->submissionid);
// Export details of how the assessment forms were filled.
static::export_assessment_forms($user, $context, $subcontext, $record->id);
}
$rs->close();
}
/**
* Export the grading strategy data related to the particular assessment.
*
* @param stdClass $user User we are exporting for
* @param context $context Workshop activity content
* @param array $subcontext Subcontext path of the assessment
* @param int $assessmentid ID of the exported assessment
*/
protected static function export_assessment_forms(\stdClass $user, \context $context, array $subcontext, $assessmentid) {
foreach (\workshop::available_strategies_list() as $strategy => $title) {
$providername = '\workshopform_'.$strategy.'\privacy\provider';
if (is_subclass_of($providername, '\mod_workshop\privacy\workshopform_provider')) {
component_class_callback($providername, 'export_assessment_form',
[
$user,
$context,
array_merge($subcontext, [get_string('assessmentform', 'mod_workshop'), $title]),
$assessmentid,
]
);
} else {
debugging('Missing class '.$providername.' implementing workshopform_provider interface', DEBUG_DEVELOPER);
}
}
}
/**
* Delete personal data for all users in the context.
*
* @param context $context Context to delete personal data from.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $CFG, $DB;
require_once($CFG->libdir.'/gradelib.php');
if ($context->contextlevel != CONTEXT_MODULE) {
return;
}
$cm = get_coursemodule_from_id('workshop', $context->instanceid, 0, false, IGNORE_MISSING);
if (!$cm) {
// Probably some kind of expired context.
return;
}
$workshop = $DB->get_record('workshop', ['id' => $cm->instance], 'id, course', MUST_EXIST);
$submissions = $DB->get_records('workshop_submissions', ['workshopid' => $workshop->id], '', 'id');
$assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
$DB->delete_records('workshop_aggregations', ['workshopid' => $workshop->id]);
$DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
$DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
$DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_workshop', 'submission_content');
$fs->delete_area_files($context->id, 'mod_workshop', 'submission_attachment');
$fs->delete_area_files($context->id, 'mod_workshop', 'overallfeedback_content');
$fs->delete_area_files($context->id, 'mod_workshop', 'overallfeedback_attachment');
grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, ['reset' => true]);
grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, ['reset' => true]);
\core_plagiarism\privacy\provider::delete_plagiarism_for_context($context);
}
/**
* Delete personal data for the user in a list of contexts.
*
* Removing assessments of submissions from the Workshop is not trivial. Removing one user's data can easily affect
* other users' grades and completion criteria. So we replace the non-essential contents with a "deleted" message,
* but keep the actual info in place. The argument is that one's right for privacy should not overweight others'
* right for accessing their own personal data and be evaluated on their basis.
*
* @param approved_contextlist $contextlist List of contexts to delete data from.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$user = $contextlist->get_user();
$fs = get_file_storage();
// Replace sensitive data in all submissions by the user in the given contexts.
$sql = "SELECT ws.id AS submissionid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
WHERE ctx.id {$contextsql}
AND ws.authorid = :authorid";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'authorid' => $user->id,
];
$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);
foreach ($contextlist->get_contextids() as $contextid) {
$fs->delete_area_files_select($contextid, 'mod_workshop', 'submission_content',
$submissionidsql, $submissionidparams);
$fs->delete_area_files_select($contextid, 'mod_workshop', 'submission_attachment',
$submissionidsql, $submissionidparams);
}
}
// Replace personal data in received assessments - feedback is seen as belonging to the recipient.
$sql = "SELECT wa.id AS assessmentid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE ctx.id {$contextsql}
AND ws.authorid = :authorid";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'authorid' => $user->id,
];
$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);
foreach ($contextlist->get_contextids() as $contextid) {
$fs->delete_area_files_select($contextid, 'mod_workshop', 'overallfeedback_content',
$assessmentidsql, $assessmentidparams);
$fs->delete_area_files_select($contextid, 'mod_workshop', 'overallfeedback_attachment',
$assessmentidsql, $assessmentidparams);
}
}
// Replace sensitive data in provided assessments records.
$sql = "SELECT wa.id AS assessmentid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE ctx.id {$contextsql}
AND wa.reviewerid = :reviewerid";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'reviewerid' => $user->id,
];
$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 ($contextlist as $context) {
\core_plagiarism\privacy\provider::delete_plagiarism_for_user($user->id, $context);
}
}
}
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides {@link mod_workshop\privacy\workshopform_legacy_polyfill} trait.
*
* @package mod_workshop
* @category privacy
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Trait allowing additional (contrib) plugins to have single codebase for 3.3 and 3.4.
*
* The signature of the method in the {@link \mod_workshop\privacy\workshopform_provider} interface makes use of scalar
* type hinting that is available in PHP 7.0 only. If a plugin wants to implement the interface in 3.3 (and therefore
* PHP 5.6) with the same codebase, they can make use of this trait. Instead of implementing the interface directly, the
* workshopform plugin can implement the required logic in the method (note the underscore and missing "int" hint):
*
* public static function _export_assessment_form(\stdClass $user, \context $context, array $subcontext, $assessmentid)
*
* and then simply use this trait in their provider class.
*
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait workshopform_legacy_polyfill {
/**
* Return details of the filled assessment form.
*
* @param stdClass $user User we are exporting data for
* @param context $context The workshop activity context
* @param array $subcontext Subcontext within the context to export to
* @param int $assessmentid ID of the assessment
*/
public static function export_assessment_form(\stdClass $user, \context $context, array $subcontext, $assessmentid) {
return static::_export_assessment_form($user, $context, $subcontext, $assessmentid);
}
}
@@ -0,0 +1,47 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides {@link mod_workshop\privacy\workshopform_provider} interface.
*
* @package mod_workshop
* @category privacy
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for grading strategy subplugins implementing the privacy API.
*
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface workshopform_provider extends \core_privacy\local\request\plugin\subplugin_provider {
/**
* Return details of the filled assessment form.
*
* @param stdClass $user User we are exporting data for
* @param context $context The workshop activity context
* @param array $subcontext Subcontext within the context to export to
* @param int $assessmentid ID of the assessment
*/
public static function export_assessment_form(\stdClass $user, \context $context, array $subcontext, $assessmentid);
}
+42
View File
@@ -64,6 +64,7 @@ $string['assessmentofsubmission'] = '<a href="{$a->assessmenturl}">Assessment</a
$string['assessmentreference'] = 'Reference assessment';
$string['assessmentreferenceconflict'] = 'It is not possible to assess an example submission for which you provided a reference assessment.';
$string['assessmentreferenceneeded'] = 'You have to assess this example submission to provide a reference assessment. Click \'Continue\' button to assess the submission.';
$string['assessments'] = 'Assessments';
$string['assessmentsettings'] = 'Assessment settings';
$string['assessmentstart'] = 'Open for assessment from';
$string['assessmentstartevent'] = '{$a} (opens for assessment)';
@@ -196,6 +197,7 @@ Submissions are assessed using a multi-criteria assessment form defined by the t
Students obtain two grades in a workshop activity - a grade for their submission and a grade for their assessment of their peers\' submissions. Both grades are recorded in the gradebook.';
$string['modulename_link'] = 'mod/workshop/view';
$string['modulenameplural'] = 'Workshops';
$string['myassessments'] = 'My assessments';
$string['mysubmission'] = 'My submission';
$string['nattachments'] = 'Maximum number of submission attachments';
$string['noexamples'] = 'No examples yet in this workshop';
@@ -232,6 +234,46 @@ $string['pluginadministration'] = 'Workshop administration';
$string['pluginname'] = 'Workshop';
$string['prepareexamples'] = 'Prepare example submissions';
$string['previewassessmentform'] = 'Preview';
$string['privacy:metadata:aggregatedgradinggrade'] = 'Aggregated grade for all assessments made by the user in the given workshop activity';
$string['privacy:metadata:assessmentgrade'] = 'Aggregated grade for the submission suggested by this assessment';
$string['privacy:metadata:assessmentgradinggrade'] = 'Grade for providing this assessment';
$string['privacy:metadata:assessmentgradinggradeover'] = 'Manually overridden value of the grade for providing this assessment';
$string['privacy:metadata:assessmentid'] = 'Identifier of the assessment';
$string['privacy:metadata:authorid'] = 'Identifier of the submission author';
$string['privacy:metadata:dimensiongrade'] = 'Grade in the given assessment dimension';
$string['privacy:metadata:dimensionid'] = 'Identifier of the assessment dimension';
$string['privacy:metadata:example'] = 'Whether this record represents an example submission';
$string['privacy:metadata:feedbackauthor'] = 'Feedback for the author';
$string['privacy:metadata:feedbackauthorformat'] = 'Text format of the feedback for the author';
$string['privacy:metadata:feedbackreviewer'] = 'Feedback for the user providing the assessment';
$string['privacy:metadata:feedbackreviewerformat'] = 'Text format of the feedback for the user providing the assessment';
$string['privacy:metadata:late'] = 'Whether the submission been submitted after the deadline';
$string['privacy:metadata:peercomment'] = 'Comment on the given grade by the user providing the assessment';
$string['privacy:metadata:peercommentformat'] = 'Text format of the comment on the given grade';
$string['privacy:metadata:preference:perpage'] = 'Number of submissions the user prefers to see on one page';
$string['privacy:metadata:published'] = 'Whether the submission should be published to all participants once the workshop is closed';
$string['privacy:metadata:reviewerid'] = 'Identifier of the user providing the assessment';
$string['privacy:metadata:strategy'] = 'Name of the grading strategy subplugin interpreting the record values';
$string['privacy:metadata:submissioncontent'] = 'Content of the submission';
$string['privacy:metadata:submissioncontentformat'] = 'Text format of the submission content';
$string['privacy:metadata:submissiongrade'] = 'Aggregated grade for the submission written as a decimal number from interval 0..100';
$string['privacy:metadata:submissiongradeover'] = 'Manually overridden value of the aggregated grade';
$string['privacy:metadata:submissionid'] = 'Identifier of the submission';
$string['privacy:metadata:submissiontitle'] = 'Title of the submission';
$string['privacy:metadata:subsystem:corefiles'] = 'Workshop module stores files embedded in / attached to the submission text';
$string['privacy:metadata:subsystem:coreplagiarism'] = 'Workshop module has inbuilt support for plagiarism prevention systems';
$string['privacy:metadata:timeaggregated'] = 'When the aggregated grade was last calculated';
$string['privacy:metadata:timecreated'] = 'When this record was created in the database';
$string['privacy:metadata:timemodified'] = 'When this record was last modified in the database';
$string['privacy:metadata:userid'] = 'Identifier of the user for which aggregated grade is calculated';
$string['privacy:metadata:weight'] = 'Weight of the assessment';
$string['privacy:metadata:workshopaggregations'] = 'Holds aggregated grades for assessment';
$string['privacy:metadata:workshopassessments'] = 'Holds information about allocated assessments of workshop module submissions';
$string['privacy:metadata:workshopgrades'] = 'Holds information about how the assessment forms were filled with grades and comments';
$string['privacy:metadata:workshopid'] = 'Identifier 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['publishedsubmissions'] = 'Published submissions';
$string['publishsubmission'] = 'Publish submission';
$string['publishsubmission_help'] = 'Published submissions are available to the others when the workshop is closed.';
+2
View File
@@ -152,6 +152,8 @@ class mod_workshop_generator extends testing_module_generator {
'timecreated' => $timenow,
'timemodified' => $timenow,
'grade' => null,
'feedbackauthor' => '',
'feedbackreviewer' => '',
);
$id = $DB->insert_record('workshop_assessments', $record);
@@ -0,0 +1,379 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link mod_workshop_privacy_provider_testcase} class.
*
* @package mod_workshop
* @category test
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\local\request\writer;
/**
* Unit tests for the privacy API implementation.
*
* @copyright 2018 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_workshop_privacy_provider_testcase extends advanced_testcase {
/** @var testing_data_generator */
protected $generator;
/** @var mod_workshop_generator */
protected $workshopgenerator;
/** @var stdClass */
protected $course1;
/** @var stdClass */
protected $course2;
/** @var stdClass */
protected $student1;
/** @var stdClass */
protected $student2;
/** @var stdClass */
protected $student3;
/** @var stdClass */
protected $teacher4;
/** @var stdClass first workshop in course1 */
protected $workshop11;
/** @var stdClass second workshop in course1 */
protected $workshop12;
/** @var stdClass first workshop in course2 */
protected $workshop21;
/** @var int ID of the submission in workshop11 by student1 */
protected $submission111;
/** @var int ID of the submission in workshop12 by student1 */
protected $submission121;
/** @var int ID of the submission in workshop12 by student2 */
protected $submission122;
/** @var int ID of the submission in workshop21 by student2 */
protected $submission212;
/** @var int ID of the assessment of submission111 by student1 */
protected $assessment1111;
/** @var int ID of the assessment of submission111 by student2 */
protected $assessment1112;
/** @var int ID of the assessment of submission111 by student3 */
protected $assessment1113;
/** @var int ID of the assessment of submission121 by student2 */
protected $assessment1212;
/** @var int ID of the assessment of submission212 by student1 */
protected $assessment2121;
/**
* Set up the test environment.
*
* course1
* |
* +--workshop11 (first digit matches the course, second is incremental)
* | |
* | +--submission111 (first two digits match the workshop, last one matches the author)
* | |
* | +--assessment1111 (first three digits match the submission, last one matches the reviewer)
* | +--assessment1112
* | +--assessment1113
* |
* +--workshop12
* |
* +--submission121
* | |
* | +--assessment1212
* |
* +--submission122
*
* etc.
*/
protected function setUp() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$this->generator = $this->getDataGenerator();
$this->workshopgenerator = $this->generator->get_plugin_generator('mod_workshop');
$this->course1 = $this->generator->create_course();
$this->course2 = $this->generator->create_course();
$this->workshop11 = $this->generator->create_module('workshop', [
'course' => $this->course1,
'name' => 'Workshop11',
]);
$DB->set_field('workshop', 'phase', 50, ['id' => $this->workshop11->id]);
$this->workshop12 = $this->generator->create_module('workshop', ['course' => $this->course1]);
$this->workshop21 = $this->generator->create_module('workshop', ['course' => $this->course2]);
$this->student1 = $this->generator->create_user();
$this->student2 = $this->generator->create_user();
$this->student3 = $this->generator->create_user();
$this->teacher4 = $this->generator->create_user();
$this->submission111 = $this->workshopgenerator->create_submission($this->workshop11->id, $this->student1->id);
$this->submission121 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student1->id,
['gradeoverby' => $this->teacher4->id]);
$this->submission122 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student2->id);
$this->submission212 = $this->workshopgenerator->create_submission($this->workshop21->id, $this->student2->id);
$this->assessment1111 = $this->workshopgenerator->create_assessment($this->submission111, $this->student1->id, [
'grade' => null,
]);
$this->assessment1112 = $this->workshopgenerator->create_assessment($this->submission111, $this->student2->id, [
'grade' => 92,
]);
$this->assessment1113 = $this->workshopgenerator->create_assessment($this->submission111, $this->student3->id);
$this->assessment1212 = $this->workshopgenerator->create_assessment($this->submission121, $this->student2->id, [
'feedbackauthor' => 'This is what student 2 thinks about submission 121',
'feedbackreviewer' => 'This is what the teacher thinks about this assessment',
]);
$this->assessment2121 = $this->workshopgenerator->create_assessment($this->submission212, $this->student1->id, [
'grade' => 68,
'gradinggradeover' => 80,
'gradinggradeoverby' => $this->teacher4->id,
'feedbackauthor' => 'This is what student 1 thinks about submission 212',
'feedbackreviewer' => 'This is what the teacher thinks about this assessment',
]);
}
/**
* Test {@link \mod_workshop\privacy\provider::get_contexts_for_userid()} implementation.
*/
public function test_get_contexts_for_userid() {
$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);
// Student1 has data in workshop11 (author + self reviewer), workshop12 (author) and workshop21 (reviewer).
$contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student1->id);
$this->assertInstanceOf(\core_privacy\local\request\contextlist::class, $contextlist);
$this->assertEquals([$context11->id, $context12->id, $context21->id], $contextlist->get_contextids(), null, 0.0, 10, true);
// Student2 has data in workshop11 (reviewer), workshop12 (reviewer) and workshop21 (author).
$contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student2->id);
$this->assertEquals([$context11->id, $context12->id, $context21->id], $contextlist->get_contextids(), null, 0.0, 10, true);
// Student3 has data in workshop11 (reviewer).
$contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student3->id);
$this->assertEquals([$context11->id], $contextlist->get_contextids(), null, 0.0, 10, true);
// Teacher4 has data in workshop12 (gradeoverby) and workshop21 (gradinggradeoverby).
$contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->teacher4->id);
$this->assertEquals([$context21->id, $context12->id], $contextlist->get_contextids(), null, 0.0, 10, true);
}
/**
* Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation.
*/
public function test_export_user_data_1() {
$contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [
\context_module::instance($this->workshop11->cmid)->id,
\context_module::instance($this->workshop12->cmid)->id,
]);
\mod_workshop\privacy\provider::export_user_data($contextlist);
$writer = writer::with_context(\context_module::instance($this->workshop11->cmid));
$workshop = $writer->get_data([]);
$this->assertEquals('Workshop11', $workshop->name);
$this->assertObjectHasAttribute('phase', $workshop);
$mysubmission = $writer->get_data([
get_string('mysubmission', 'mod_workshop'),
]);
$mysubmissionselfassessmentwithoutgrade = $writer->get_data([
get_string('mysubmission', 'mod_workshop'),
get_string('assessments', 'mod_workshop'),
$this->assessment1111,
]);
$this->assertNull($mysubmissionselfassessmentwithoutgrade->grade);
$this->assertEquals(get_string('yes'), $mysubmissionselfassessmentwithoutgrade->selfassessment);
$mysubmissionassessmentwithgrade = $writer->get_data([
get_string('mysubmission', 'mod_workshop'),
get_string('assessments', 'mod_workshop'),
$this->assessment1112,
]);
$this->assertEquals(92, $mysubmissionassessmentwithgrade->grade);
$this->assertEquals(get_string('no'), $mysubmissionassessmentwithgrade->selfassessment);
$mysubmissionassessmentwithoutgrade = $writer->get_data([
get_string('mysubmission', 'mod_workshop'),
get_string('assessments', 'mod_workshop'),
$this->assessment1113,
]);
$this->assertEquals(null, $mysubmissionassessmentwithoutgrade->grade);
$this->assertEquals(get_string('no'), $mysubmissionassessmentwithoutgrade->selfassessment);
$myassessments = $writer->get_data([
get_string('myassessments', 'mod_workshop'),
]);
$this->assertEmpty($myassessments);
}
/**
* Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation.
*/
public function test_export_user_data_2() {
$contextlist = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_workshop', [
\context_module::instance($this->workshop11->cmid)->id,
]);
\mod_workshop\privacy\provider::export_user_data($contextlist);
$writer = writer::with_context(\context_module::instance($this->workshop11->cmid));
$assessedsubmission = $writer->get_related_data([
get_string('myassessments', 'mod_workshop'),
$this->assessment1112,
], 'submission');
$this->assertEquals(get_string('no'), $assessedsubmission->myownsubmission);
}
/**
* Test {@link \mod_workshop\privacy\provider::delete_data_for_all_users_in_context()} implementation.
*/
public function test_delete_data_for_all_users_in_context() {
global $DB;
$this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));
// Passing a non-module context does nothing.
\mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_course::instance($this->course1->id));
$this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));
// Passing a workshop context removes all data.
\mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_module::instance($this->workshop11->cmid));
$this->assertFalse($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));
}
/**
* Test {@link \mod_workshop\privacy\provider::delete_data_for_user()} implementation.
*/
public function test_delete_data_for_user() {
global $DB;
$student1submissions = $DB->get_records('workshop_submissions', [
'workshopid' => $this->workshop12->id,
'authorid' => $this->student1->id,
]);
$student2submissions = $DB->get_records('workshop_submissions', [
'workshopid' => $this->workshop12->id,
'authorid' => $this->student2->id,
]);
$this->assertNotEmpty($student1submissions);
$this->assertNotEmpty($student2submissions);
foreach ($student1submissions as $submission) {
$this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
}
foreach ($student2submissions as $submission) {
$this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
}
$contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [
\context_module::instance($this->workshop12->cmid)->id,
\context_module::instance($this->workshop21->cmid)->id,
]);
\mod_workshop\privacy\provider::delete_data_for_user($contextlist);
$student1submissions = $DB->get_records('workshop_submissions', [
'workshopid' => $this->workshop12->id,
'authorid' => $this->student1->id,
]);
$student2submissions = $DB->get_records('workshop_submissions', [
'workshopid' => $this->workshop12->id,
'authorid' => $this->student2->id,
]);
$this->assertNotEmpty($student1submissions);
$this->assertNotEmpty($student2submissions);
foreach ($student1submissions as $submission) {
$this->assertEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
}
foreach ($student2submissions as $submission) {
$this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
}
$student1assessments = $DB->get_records('workshop_assessments', [
'submissionid' => $this->submission212,
'reviewerid' => $this->student1->id,
]);
$this->assertNotEmpty($student1assessments);
foreach ($student1assessments as $assessment) {
// In Moodle, feedback is seen to belong to the recipient user.
$this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
$this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
// We delete what we can without affecting others' grades.
$this->assertEquals(68, $assessment->grade);
}
$assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($student1submissions));
$this->assertNotEmpty($assessments);
foreach ($assessments as $assessment) {
if ($assessment->reviewerid == $this->student1->id) {
$this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
$this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
} else {
$this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
$this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
}
}
}
}