MDL-63591 core_grades: updated privacy provider

This commit is contained in:
Mark Nelson 2018-09-27 11:40:18 +10:00
parent ac81c98648
commit b564a55153
3 changed files with 676 additions and 47 deletions

View File

@ -129,6 +129,8 @@ class provider implements
'importonlyfeedback' => 'privacy:metadata:grade_import_values:importonlyfeedback'
], 'privacy:metadata:grade_import_values');
$collection->link_subsystem('core_files', 'privacy:metadata:filepurpose');
return $collection;
}
@ -482,11 +484,24 @@ class provider implements
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
$context = context_course::instance($record->gi_courseid);
$gg = static::extract_grade_grade_from_record($record);
$carry[] = static::transform_grade($gg, $context);
$carry[] = static::transform_grade($gg, $context, false);
return $carry;
}, function($courseid, $data) use ($rootpath) {
$context = context_course::instance($courseid);
$pathtofiles = [
get_string('grades', 'core_grades'),
get_string('feedbackfiles', 'core_grades')
];
foreach ($data as $key => $grades) {
$gg = $grades['gradeobject'];
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
GRADE_FEEDBACK_FILEAREA, $gg->id);
unset($data[$key]['gradeobject']); // Do not want to export this later.
}
writer::with_context($context)->export_data($rootpath, (object) ['grades' => $data]);
});
@ -508,13 +523,25 @@ class provider implements
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
$context = context_course::instance($record->gi_courseid);
$gg = static::extract_grade_grade_from_record($record, true);
$carry[] = array_merge(static::transform_grade($gg, $context), [
$carry[] = array_merge(static::transform_grade($gg, $context, true), [
'action' => static::transform_history_action($record->ggh_action)
]);
return $carry;
}, function($courseid, $data) use ($rootpath) {
$context = context_course::instance($courseid);
$pathtofiles = [
get_string('grades', 'core_grades'),
get_string('feedbackhistoryfiles', 'core_grades')
];
foreach ($data as $key => $grades) {
$gg = $grades['gradeobject'];
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
GRADE_HISTORY_FEEDBACK_FILEAREA, $gg->historyid);
unset($data[$key]['gradeobject']); // Do not want to export this later.
}
writer::with_context($context)->export_related_data($rootpath, 'history', (object) ['grades' => $data]);
});
@ -585,7 +612,7 @@ class provider implements
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
$context = context_course::instance($record->gi_courseid);
$gg = static::extract_grade_grade_from_record($record);
$carry[] = array_merge(static::transform_grade($gg, $context), [
$carry[] = array_merge(static::transform_grade($gg, $context, false), [
'userid' => transform::user($gg->userid),
'created_or_modified_by_you' => transform::yesno(true),
]);
@ -593,6 +620,18 @@ class provider implements
}, function($courseid, $data) use ($relatedtomepath) {
$context = context_course::instance($courseid);
$pathtofiles = [
get_string('grades', 'core_grades'),
get_string('feedbackfiles', 'core_grades')
];
foreach ($data as $key => $grades) {
$gg = $grades['gradeobject'];
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
GRADE_FEEDBACK_FILEAREA, $gg->id);
unset($data[$key]['gradeobject']); // Do not want to export this later.
}
writer::with_context($context)->export_related_data($relatedtomepath, 'grades', (object) ['grades' => $data]);
});
@ -614,7 +653,7 @@ class provider implements
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) use ($userid) {
$context = context_course::instance($record->gi_courseid);
$gg = static::extract_grade_grade_from_record($record, true);
$carry[] = array_merge(static::transform_grade($gg, $context), [
$carry[] = array_merge(static::transform_grade($gg, $context, true), [
'userid' => transform::user($gg->userid),
'logged_in_user_was_you' => transform::yesno($userid == $record->loggeduser),
'author_of_change_was_you' => transform::yesno($userid == $gg->usermodified),
@ -624,6 +663,18 @@ class provider implements
}, function($courseid, $data) use ($relatedtomepath) {
$context = context_course::instance($courseid);
$pathtofiles = [
get_string('grades', 'core_grades'),
get_string('feedbackhistoryfiles', 'core_grades')
];
foreach ($data as $key => $grades) {
$gg = $grades['gradeobject'];
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
GRADE_HISTORY_FEEDBACK_FILEAREA, $gg->historyid);
unset($data[$key]['gradeobject']); // Do not want to export this later.
}
writer::with_context($context)->export_related_data($relatedtomepath, 'grades_history',
(object) ['modified_records' => $data]);
});
@ -649,6 +700,10 @@ class provider implements
if (empty($itemids)) {
return;
}
self::delete_files($itemids, true);
self::delete_files($itemids, false);
list($insql, $inparams) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
$DB->delete_records_select('grade_grades', "itemid $insql", $inparams);
$DB->delete_records_select('grade_grades_history', "itemid $insql", $inparams);
@ -684,9 +739,14 @@ class provider implements
return;
}
// Delete all the files.
self::delete_files($itemids, true, [$userid]);
self::delete_files($itemids, false, [$userid]);
// Delete all the grades.
list($insql, $inparams) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
$params = array_merge($inparams, ['userid' => $userid]);
$DB->delete_records_select('grade_grades', "itemid $insql AND userid = :userid", $params);
$DB->delete_records_select('grade_grades_history', "itemid $insql AND userid = :userid", $params);
}
@ -720,10 +780,15 @@ class provider implements
return;
}
// Delete all the files.
self::delete_files($itemids, true, $userids);
self::delete_files($itemids, false, $userids);
// 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);
}
@ -748,6 +813,21 @@ class provider implements
return;
}
list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
// First, let's delete their files.
$sql = "
SELECT gi.id
FROM {grade_grades_history} ggh
JOIN {grade_items} gi
ON gi.id = ggh.itemid
WHERE ggh.userid = :userid";
$params = ['userid' => $userid];
$gradeitems = $DB->get_records_sql($sql, $params);
if ($gradeitems) {
$itemids = array_keys($gradeitems);
self::delete_files($itemids, true, [$userid]);
}
$DB->delete_records_select('grade_grades_history', "id $insql", $inparams);
}
@ -951,6 +1031,7 @@ class provider implements
$ggrecord = static::extract_record($record, $prefix);
if ($ishistory) {
// The grade history is not a real grade_grade so we remove the ID.
$historyid = $ggrecord->id;
unset($ggrecord->id);
}
$gg = new grade_grade($ggrecord, false);
@ -968,6 +1049,10 @@ class provider implements
$gi->scale->load_items();
}
if ($ishistory) {
$gg->historyid = $historyid;
}
return $gg;
}
@ -1097,13 +1182,32 @@ class provider implements
*
* @param grade_grade $gg The grade object.
* @param context $context The context.
* @param bool $ishistory Whether we're extracting a historical grade.
* @return array
*/
protected static function transform_grade(grade_grade $gg, context $context) {
protected static function transform_grade(grade_grade $gg, context $context, bool $ishistory) {
$gi = $gg->load_grade_item();
$timemodified = $gg->timemodified ? transform::datetime($gg->timemodified) : null;
$timecreated = $gg->timecreated ? transform::datetime($gg->timecreated) : $timemodified; // When null we use timemodified.
$filearea = $ishistory ? GRADE_HISTORY_FEEDBACK_FILEAREA : GRADE_FEEDBACK_FILEAREA;
$itemid = $ishistory ? $gg->historyid : $gg->id;
$subpath = $ishistory ? get_string('feedbackhistoryfiles', 'core_grades') : get_string('feedbackfiles', 'core_grades');
$pathtofiles = [
get_string('grades', 'core_grades'),
$subpath
];
$gg->feedback = writer::with_context($gg->get_context())->rewrite_pluginfile_urls(
$pathtofiles,
GRADE_FILE_COMPONENT,
$filearea,
$itemid,
$gg->feedback
);
return [
'gradeobject' => $gg,
'item' => $gi->get_name(),
'grade' => $gg->finalgrade,
'grade_formatted' => grade_format_gradevalue($gg->finalgrade, $gi),
@ -1114,4 +1218,56 @@ class provider implements
];
}
/**
* Handles deleting files for a given list of grade items.
*
* If an array of userids if given then it handles deleting files for those users.
*
* @param array $itemids
* @param bool $ishistory
* @param array|null $userids
* @throws \coding_exception
* @throws \dml_exception
*/
protected static function delete_files(array $itemids, bool $ishistory, array $userids = null) {
global $DB;
list($iteminnsql, $params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
if (!is_null($userids)) {
list($userinnsql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params = array_merge($params, $userparams);
}
if ($ishistory) {
$gradefields = static::get_fields_sql('grade_grades_history', 'ggh', 'ggh_');
$gradetable = 'grade_grades_history';
$tableprefix = 'ggh';
$filearea = GRADE_HISTORY_FEEDBACK_FILEAREA;
} else {
$gradefields = static::get_fields_sql('grade_grade', 'gg', 'gg_');
$gradetable = 'grade_grades';
$tableprefix = 'gg';
$filearea = GRADE_FEEDBACK_FILEAREA;
}
$gifields = static::get_fields_sql('grade_item', 'gi', 'gi_');
$fs = new \file_storage();
$sql = "SELECT $gradefields, $gifields
FROM {{$gradetable}} $tableprefix
JOIN {grade_items} gi
ON gi.id = {$tableprefix}.itemid
WHERE gi.id $iteminnsql ";
if (!is_null($userids)) {
$sql .= "AND {$tableprefix}.userid $userinnsql";
}
$grades = $DB->get_recordset_sql($sql, $params);
foreach ($grades as $grade) {
$gg = static::extract_grade_grade_from_record($grade, $ishistory);
$fileitemid = ($ishistory) ? $gg->historyid : $gg->id;
$fs->delete_area_files($gg->get_context()->id, GRADE_FILE_COMPONENT, $filearea, $fileitemid);
}
$grades->close();
}
}

View File

@ -404,6 +404,9 @@ class core_grades_privacy_testcase extends provider_testcase {
public function test_delete_data_for_all_users_in_context() {
global $DB;
$fs = new file_storage();
$dg = $this->getDataGenerator();
$c1 = $dg->create_course();
@ -414,21 +417,118 @@ class core_grades_privacy_testcase extends provider_testcase {
$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);
$a1 = $dg->create_module('assign', ['course' => $c1->id]);
$a2 = $dg->create_module('assign', ['course' => $c1->id]);
$a3 = $dg->create_module('assign', ['course' => $c2->id]);
$a4 = $dg->create_module('assign', ['course' => $c2->id]);
$gi1a->update_final_grade($u1->id, 1, 'test');
$gi1a->update_final_grade($u2->id, 1, 'test');
$gi1b->update_final_grade($u1->id, 1, 'test');
$gi2a->update_final_grade($u1->id, 1, 'test');
$gi2a->update_final_grade($u2->id, 1, 'test');
$gi2b->update_final_grade($u1->id, 1, 'test');
$gi2b->update_final_grade($u2->id, 1, 'test');
$a1context = context_module::instance($a1->cmid);
$a2context = context_module::instance($a2->cmid);
$a3context = context_module::instance($a3->cmid);
$a4context = context_module::instance($a4->cmid);
// Create some stuff.
$gi1a = new grade_item($dg->create_grade_item(
[
'courseid' => $c1->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a1->id
]
), false);
$gi1b = new grade_item($dg->create_grade_item(
[
'courseid' => $c1->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a2->id
]
), false);
$gi2a = new grade_item($dg->create_grade_item(
[
'courseid' => $c2->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a3->id
]
), false);
$gi2b = new grade_item($dg->create_grade_item(
[
'courseid' => $c2->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a4->id
]
), false);
$this->add_feedback_file_to_copy();
$grades['feedback'] = 'Nice feedback!';
$grades['feedbackformat'] = FORMAT_MOODLE;
$grades['feedbackfiles'] = [
'contextid' => 1,
'component' => 'test',
'filearea' => 'testarea',
'itemid' => 1
];
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi1a->courseid, $gi1a->itemtype, $gi1a->itemmodule, $gi1a->iteminstance,
$gi1a->itemnumber, $grades);
$grades['userid'] = $u2->id;
grade_update('mod/assign', $gi1a->courseid, $gi1a->itemtype, $gi1a->itemmodule, $gi1a->iteminstance,
$gi1a->itemnumber, $grades);
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi1b->courseid, $gi1b->itemtype, $gi1b->itemmodule, $gi1b->iteminstance,
$gi1b->itemnumber, $grades);
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi2a->courseid, $gi2a->itemtype, $gi2a->itemmodule, $gi2a->iteminstance,
$gi2a->itemnumber, $grades);
$grades['userid'] = $u2->id;
grade_update('mod/assign', $gi2a->courseid, $gi2a->itemtype, $gi2a->itemmodule, $gi2a->iteminstance,
$gi2a->itemnumber, $grades);
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi2b->courseid, $gi2b->itemtype, $gi2b->itemmodule, $gi2b->iteminstance,
$gi2b->itemnumber, $grades);
$grades['userid'] = $u2->id;
grade_update('mod/assign', $gi2b->courseid, $gi2b->itemtype, $gi2b->itemmodule, $gi2b->iteminstance,
$gi2b->itemnumber, $grades);
$gi2b->delete();
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$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' => $u1->id, 'itemid' => $gi1b->id]));
@ -446,20 +546,107 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
provider::delete_data_for_all_users_in_context($u1ctx);
$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->assertFalse($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// The user context is only reported when there are orphan historical grades, so we only delete those files.
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// User 2 still has historical files.
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
provider::delete_data_for_all_users_in_context($c2ctx);
$this->assertFalse($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->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
}
public function test_delete_data_for_user() {
global $DB;
$fs = new file_storage();
$dg = $this->getDataGenerator();
$c1 = $dg->create_course();
@ -471,19 +658,88 @@ class core_grades_privacy_testcase extends provider_testcase {
$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);
$a1 = $dg->create_module('assign', ['course' => $c1->id]);
$a2 = $dg->create_module('assign', ['course' => $c1->id]);
$a3 = $dg->create_module('assign', ['course' => $c2->id]);
$a4 = $dg->create_module('assign', ['course' => $c2->id]);
$a1context = context_module::instance($a1->cmid);
$a2context = context_module::instance($a2->cmid);
$a3context = context_module::instance($a3->cmid);
$a4context = context_module::instance($a4->cmid);
// Create some stuff.
$gi1a = new grade_item($dg->create_grade_item(
[
'courseid' => $c1->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a1->id
]
), false);
$gi1b = new grade_item($dg->create_grade_item(
[
'courseid' => $c1->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a2->id
]
), false);
$gi2a = new grade_item($dg->create_grade_item(
[
'courseid' => $c2->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a3->id
]
), false);
$gi2b = new grade_item($dg->create_grade_item(
[
'courseid' => $c2->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a4->id
]
), false);
$this->add_feedback_file_to_copy();
$grades['feedback'] = 'Nice feedback!';
$grades['feedbackfiles'] = [
'contextid' => 1,
'component' => 'test',
'filearea' => 'testarea',
'itemid' => 1
];
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi1a->courseid, $gi1a->itemtype, $gi1a->itemmodule, $gi1a->iteminstance,
$gi1a->itemnumber, $grades);
$grades['userid'] = $u2->id;
grade_update('mod/assign', $gi1a->courseid, $gi1a->itemtype, $gi1a->itemmodule, $gi1a->iteminstance,
$gi1a->itemnumber, $grades);
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi1b->courseid, $gi1b->itemtype, $gi1b->itemmodule, $gi1b->iteminstance,
$gi1b->itemnumber, $grades);
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi2a->courseid, $gi2a->itemtype, $gi2a->itemmodule, $gi2a->iteminstance,
$gi2a->itemnumber, $grades);
$grades['userid'] = $u2->id;
grade_update('mod/assign', $gi2a->courseid, $gi2a->itemtype, $gi2a->itemmodule, $gi2a->iteminstance,
$gi2a->itemnumber, $grades);
$grades['userid'] = $u1->id;
grade_update('mod/assign', $gi2b->courseid, $gi2b->itemtype, $gi2b->itemmodule, $gi2b->iteminstance,
$gi2b->itemnumber, $grades);
$grades['userid'] = $u2->id;
grade_update('mod/assign', $gi2b->courseid, $gi2b->itemtype, $gi2b->itemmodule, $gi2b->iteminstance,
$gi2b->itemnumber, $grades);
$gi1a->update_final_grade($u1->id, 1, 'test');
$gi1a->update_final_grade($u2->id, 1, 'test');
$gi1b->update_final_grade($u1->id, 1, 'test');
$gi2a->update_final_grade($u1->id, 1, 'test');
$gi2a->update_final_grade($u2->id, 1, 'test');
$gi2b->update_final_grade($u1->id, 1, 'test');
$gi2b->update_final_grade($u2->id, 1, 'test');
$gi2b->delete();
$this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
@ -494,6 +750,34 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
provider::delete_data_for_user(new approved_contextlist($u1, 'core_grades', [$c1ctx->id]));
$this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
$this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
@ -503,6 +787,34 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
provider::delete_data_for_user(new approved_contextlist($u1, 'core_grades', [$u1ctx->id]));
$this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
$this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
@ -512,6 +824,34 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertFalse($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(4, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
provider::delete_data_for_user(new approved_contextlist($u1, 'core_grades', [$u2ctx->id, $c2ctx->id]));
$this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
$this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
@ -520,6 +860,34 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
$this->assertFalse($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
$this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
// Feedback file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
// History file area.
$files = $fs->get_area_files($a1context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
$files = $fs->get_area_files($a2context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
$files = $fs->get_area_files($a3context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(2, count($files));
// Grade item 2 was deleted, so the associated files were as well.
$files = $fs->get_area_files($a4context->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
$this->assertEquals(0, count($files));
}
/**
@ -597,7 +965,6 @@ class core_grades_privacy_testcase extends provider_testcase {
}
public function test_export_data_for_user_about_grades_and_history() {
global $DB;
$dg = $this->getDataGenerator();
$c1 = $dg->create_course();
@ -626,17 +993,83 @@ class core_grades_privacy_testcase extends provider_testcase {
grade_category::fetch_course_category($c2->id);
$ci2 = grade_item::fetch_course_item($c2->id);
$this->add_feedback_file_to_copy();
$grades['feedbackfiles'] = [
'contextid' => 1,
'component' => 'test',
'filearea' => 'testarea',
'itemid' => 1
];
$a1 = $dg->create_module('assign', ['course' => $c1->id]);
// Create data that will sit in the user context because we will delete the grate item.
$gi1 = new grade_item($dg->create_grade_item(['courseid' => $c1->id, 'aggregationcoef2' => 1]), false);
$gi1->update_final_grade($ug1->id, 100, 'test', 'Well done!', FORMAT_PLAIN, $ua2->id);
$gi1->update_final_grade($ug1->id, 1, 'test', 'Hi', FORMAT_PLAIN, $ua2->id);
$gi1->update_final_grade($ug3->id, 12, 'test', 'Hello', FORMAT_PLAIN, $ua2->id);
$gi1 = new grade_item($dg->create_grade_item(
[
'courseid' => $c1->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a1->id,
'aggregationcoef2' => 1
]
), false);
$grades['feedback'] = 'Well done!';
$grades['feedbackformat'] = FORMAT_PLAIN;
$grades['userid'] = $ug1->id;
$grades['usermodified'] = $ua2->id;
$grades['rawgrade'] = 100;
grade_update('mod/assign', $gi1->courseid, $gi1->itemtype, $gi1->itemmodule, $gi1->iteminstance,
$gi1->itemnumber, $grades);
$grades['feedback'] = 'Hi';
$grades['userid'] = $ug1->id;
$grades['usermodified'] = $ua2->id;
$grades['rawgrade'] = 1;
grade_update('mod/assign', $gi1->courseid, $gi1->itemtype, $gi1->itemmodule, $gi1->iteminstance,
$gi1->itemnumber, $grades);
$grades['feedback'] = 'Hello';
$grades['userid'] = $ug3->id;
$grades['usermodified'] = $ua2->id;
$grades['rawgrade'] = 12;
grade_update('mod/assign', $gi1->courseid, $gi1->itemtype, $gi1->itemmodule, $gi1->iteminstance,
$gi1->itemnumber, $grades);
// Create another set for another user.
$gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
$gi2a->update_final_grade($ug1->id, 15, 'test', '', FORMAT_PLAIN, $ua2->id);
$gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
$gi2b->update_final_grade($ug1->id, 30, 'test', 'Well played!', FORMAT_PLAIN, $ua2->id);
$a2 = $dg->create_module('assign', ['course' => $c2->id]);
$a3 = $dg->create_module('assign', ['course' => $c2->id]);
$gi2a = new grade_item($dg->create_grade_item(
[
'courseid' => $c2->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a2->id
]
), false);
$gi2b = new grade_item($dg->create_grade_item(
[
'courseid' => $c2->id,
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => $a3->id
]
), false);
$grades['feedback'] = '';
$grades['userid'] = $ug1->id;
$grades['usermodified'] = $ua2->id;
$grades['rawgrade'] = 15;
grade_update('mod/assign', $gi2a->courseid, $gi2a->itemtype, $gi2a->itemmodule, $gi2a->iteminstance,
$gi2a->itemnumber, $grades);
$grades['feedback'] = 'Well played!';
$grades['userid'] = $ug1->id;
$grades['usermodified'] = $ua2->id;
$grades['rawgrade'] = 30;
grade_update('mod/assign', $gi2b->courseid, $gi2b->itemtype, $gi2b->itemmodule, $gi2b->iteminstance,
$gi2b->itemnumber, $grades);
// Export action user 1 everywhere.
provider::export_user_data(new approved_contextlist($ua1, 'core_grades', [$ug1ctx->id, $ug2ctx->id,
@ -667,6 +1100,17 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertEquals('Hello', $data->grades[1]['feedback']);
$this->assertEquals(transform::yesno(true), $data->grades[1]['created_or_modified_by_you']);
$pathtofiles = [
get_string('grades', 'core_grades'),
get_string('feedbackfiles', 'core_grades')
];
$file = writer::with_context($gi1->get_context())->get_files($pathtofiles)['feedback1.txt'];
$this->assertInstanceOf('stored_file', $file);
$this->assertEquals('feedback1.txt', $file->get_filename());
$relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);
// Here we are testing the export of history of grades that we've changed.
$data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'grades_history');
$this->assertCount(3, $data->modified_records);
@ -692,6 +1136,15 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
$this->assertEquals(transform::yesno(true), $grade['author_of_change_was_you']);
$pathtofiles = [
get_string('grades', 'core_grades'),
get_string('feedbackhistoryfiles', 'core_grades')
];
$file = writer::with_context($gi1->get_context())->get_files($pathtofiles)['feedback1.txt'];
$this->assertInstanceOf('stored_file', $file);
$this->assertEquals('feedback1.txt', $file->get_filename());
// Create a history record with logged user.
$this->setUser($ua3);
$gi1->update_final_grade($ug3->id, 50, 'test', '...', FORMAT_PLAIN, $ua2->id);
@ -712,11 +1165,11 @@ class core_grades_privacy_testcase extends provider_testcase {
provider::export_user_data(new approved_contextlist($ug1, 'core_grades', [$c1ctx->id]));
$data = writer::with_context($c1ctx)->get_data($rootpath);
$this->assert_context_has_no_data($c2ctx);
$this->assertCount(2, $data->grades);
$this->assertCount(3, $data->grades);
$grade = $data->grades[0];
$this->assertEquals($ci1->get_name(), $grade['item']);
$this->assertEquals(1, $grade['grade']);
$grade = $data->grades[1];
$grade = $data->grades[2];
$this->assertEquals($gi1->get_name(), $grade['item']);
$this->assertEquals(1, $grade['grade']);
$this->assertEquals('Hi', $grade['feedback']);
@ -726,24 +1179,24 @@ class core_grades_privacy_testcase extends provider_testcase {
provider::export_user_data(new approved_contextlist($ug1, 'core_grades', [$ug1ctx->id, $c1ctx->id, $c2ctx->id]));
$this->assert_context_has_no_data($ug1ctx);
$data = writer::with_context($c1ctx)->get_data($rootpath);
$this->assertCount(2, $data->grades);
$this->assertCount(3, $data->grades);
$grade = $data->grades[0];
$this->assertEquals($ci1->get_name(), $grade['item']);
$this->assertEquals(1, $grade['grade']);
$grade = $data->grades[1];
$grade = $data->grades[2];
$this->assertEquals($gi1->get_name(), $grade['item']);
$this->assertEquals(1, $grade['grade']);
$this->assertEquals('Hi', $grade['feedback']);
$data = writer::with_context($c2ctx)->get_data($rootpath);
$this->assertCount(3, $data->grades);
$this->assertCount(5, $data->grades);
$grade = $data->grades[0];
$this->assertEquals($ci2->get_name(), $grade['item']);
$grade = $data->grades[1];
$grade = $data->grades[3];
$this->assertEquals($gi2a->get_name(), $grade['item']);
$this->assertEquals(15, $grade['grade']);
$this->assertEquals('', $grade['feedback']);
$grade = $data->grades[2];
$grade = $data->grades[4];
$this->assertEquals($gi2b->get_name(), $grade['item']);
$this->assertEquals(30, $grade['grade']);
$this->assertEquals('Well played!', $grade['feedback']);
@ -756,10 +1209,10 @@ class core_grades_privacy_testcase extends provider_testcase {
writer::reset();
provider::export_user_data(new approved_contextlist($ug1, 'core_grades', [$ug1ctx->id, $c1ctx->id, $c2ctx->id]));
$data = writer::with_context($c1ctx)->get_data($rootpath);
$this->assertCount(1, $data->grades);
$this->assertCount(2, $data->grades);
$this->assertEquals($ci1->get_name(), $data->grades[0]['item']);
$data = writer::with_context($c2ctx)->get_data($rootpath);
$this->assertCount(3, $data->grades);
$this->assertCount(5, $data->grades);
$data = writer::with_context($ug1ctx)->get_related_data($rootpath, 'history');
$this->assertCount(3, $data->grades);
$grade = $data->grades[0];
@ -1131,4 +1584,21 @@ class core_grades_privacy_testcase extends provider_testcase {
$this->assertEmpty($data);
}
}
/**
* Creates a feedback file to copy to the gradebook area.
*/
private function add_feedback_file_to_copy() {
$dummy = array(
'contextid' => 1,
'component' => 'test',
'filearea' => 'testarea',
'itemid' => 1,
'filepath' => '/',
'filename' => 'feedback1.txt'
);
$fs = get_file_storage();
$fs->create_file_from_string($dummy, '');
}
}

View File

@ -223,7 +223,9 @@ $string['feedback'] = 'Feedback';
$string['feedback_help'] = 'This box enables any comments about the grade to be added.';
$string['feedbackadd'] = 'Add feedback';
$string['feedbackedit'] = 'Edit feedback';
$string['feedbackfiles'] = 'Feedback files';
$string['feedbackforgradeitems'] = 'Feedback for {$a}';
$string['feedbackhistoryfiles'] = 'Feedback history files';
$string['feedbacks'] = 'Feedbacks';
$string['feedbacksaved'] = 'Feedback saved';
$string['feedbackview'] = 'View feedback';
@ -615,6 +617,7 @@ $string['prefrows'] = 'Special rows';
$string['prefshow'] = 'Show/hide toggles';
$string['previewrows'] = 'Preview rows';
$string['privacy:metadata:categorieshistory'] = 'A record of previous versions of grade categories';
$string['privacy:metadata:filepurpose'] = 'Feedback files stored in the gradebook for a user.';
$string['privacy:metadata:grade_import_newitem'] = 'Temporary table for storing new grade_item names from grade import';
$string['privacy:metadata:grade_import_newitem:importcode'] = 'A unique batch code for identifying one batch of imports';
$string['privacy:metadata:grade_import_newitem:importer'] = 'User importing the data';