MDL-62169 mod_assignment: Implemented privacy providers

This commit is contained in:
Zig Tan 2018-05-03 15:57:37 +08:00
parent 446d8483a3
commit 0fed66ad0f
3 changed files with 1099 additions and 0 deletions

View File

@ -0,0 +1,475 @@
<?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/>.
/**
* Privacy Subsystem implementation for mod_assignment.
*
* @package mod_assignment
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_assignment\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\transform;
use core_privacy\local\request\writer;
use core_privacy\local\request\helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/assignment/lib.php');
/**
* Implementation of the privacy subsystem plugin provider for mod_assignment.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @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\plugin\provider,
\core_privacy\local\request\user_preference_provider {
/**
* Return the fields which contain personal data.
*
* @param collection $collection a reference to the collection to use to store the metadata.
* @return collection the updated collection of metadata items.
*/
public static function get_metadata(collection $collection) : collection {
$collection->add_database_table(
'assignment_submissions',
[
'userid' => 'privacy:metadata:assignment_submissions:userid',
'timecreated' => 'privacy:metadata:assignment_submissions:timecreated',
'timemodified' => 'privacy:metadata:assignment_submissions:timemodified',
'numfiles' => 'privacy:metadata:assignment_submissions:numfiles',
'data1' => 'privacy:metadata:assignment_submissions:data1',
'data2' => 'privacy:metadata:assignment_submissions:data2',
'grade' => 'privacy:metadata:assignment_submissions:grade',
'submissioncomment' => 'privacy:metadata:assignment_submissions:submissioncomment',
'teacher' => 'privacy:metadata:assignment_submissions:teacher',
'timemarked' => 'privacy:metadata:assignment_submissions:timemarked',
'mailed' => 'privacy:metadata:assignment_submissions:mailed'
],
'privacy:metadata:assignment_submissions'
);
// Legacy mod_assignment preferences from Moodle 2.X.
$collection->add_user_preference('assignment_filter', 'privacy:metadata:assignmentfilter');
$collection->add_user_preference('assignment_mailinfo', 'privacy:metadata:assignmentmailinfo');
$collection->add_user_preference('assignment_perpage', 'privacy:metadata:assignmentperpage');
$collection->add_user_preference('assignment_quickgrade', 'privacy:metadata:assignmentquickgrade');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid the userid.
* @return contextlist the list of contexts containing user info for the user.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
$contextlist = new contextlist();
$sql = "SELECT DISTINCT
ctx.id
FROM {context} ctx
JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
JOIN {assignment} a ON cm.instance = a.id
JOIN {assignment_submissions} s ON s.assignment = a.id
WHERE s.userid = :userid
OR s.teacher = :teacher";
$params = [
'contextmodule' => CONTEXT_MODULE,
'modulename' => 'assignment',
'userid' => $userid,
'teacher' => $userid
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Export personal data for the given approved_contextlist.
* User and context information is contained within the contextlist.
*
* @param approved_contextlist $contextlist a list of contexts approved for export.
*/
public static function export_user_data(approved_contextlist $contextlist) {
if (empty($contextlist->count())) {
return;
}
$user = $contextlist->get_user();
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel != CONTEXT_MODULE) {
continue;
}
// Cannot make use of helper::export_context_files(), need to manually export assignment details.
$assignmentdata = self::get_assignment_by_context($context);
// Get assignment details object for output.
$assignment = self::get_assignment_output($assignmentdata);
writer::with_context($context)->export_data([], $assignment);
// Check if the user has marked any assignment's submissions to determine assignment submissions to export.
$teacher = (self::has_marked_assignment_submissions($assignmentdata->id, $user->id) == true) ? true : false;
// Get the assignment submissions submitted by & marked by the user for an assignment.
$submissionsdata = self::get_assignment_submissions_by_assignment($assignmentdata->id, $user->id, $teacher);
foreach ($submissionsdata as $submissiondata) {
// Default subcontext path to export assignment submissions submitted by the user.
$subcontexts = [
get_string('privacy:submissionpath', 'mod_assignment')
];
if ($teacher == true) {
if ($submissiondata->teacher == $user->id) {
// Export assignment submissions that have been marked by the user.
$subcontexts = [
get_string('privacy:markedsubmissionspath', 'mod_assignment'),
transform::user($submissiondata->userid)
];
}
}
// Get assignment submission details object for output.
$submission = self::get_assignment_submission_output($submissiondata);
$itemid = $submissiondata->id;
writer::with_context($context)
->export_data($subcontexts, $submission)
->export_area_files($subcontexts, 'mod_assignment', 'submission', $itemid);
}
}
}
/**
* Stores the user preferences related to mod_assign.
*
* @param int $userid The user ID that we want the preferences for.
*/
public static function export_user_preferences(int $userid) {
$context = \context_system::instance();
$assignmentpreferences = [
'assignment_filter' => [
'string' => get_string('privacy:metadata:assignmentfilter', 'mod_assignment'),
'bool' => false
],
'assignment_mailinfo' => [
'string' => get_string('privacy:metadata:assignmentmailinfo', 'mod_assignment'),
'bool' => false
],
'assignment_perpage' => [
'string' => get_string('privacy:metadata:assignmentperpage', 'mod_assignment'),
'bool' => false
],
'assignment_quickgrade' => [
'string' => get_string('privacy:metadata:assignmentquickgrade', 'mod_assignment'),
'bool' => false
],
];
foreach ($assignmentpreferences as $key => $preference) {
$value = get_user_preferences($key, null, $userid);
if ($preference['bool']) {
$value = transform::yesno($value);
}
if (isset($value)) {
writer::with_context($context)
->export_user_preference('mod_assignment', $key, $value, $preference['string']);
}
}
}
/**
* Delete all data for all users in the specified context.
*
* @param \context $context the context to delete in.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if (empty($context)) {
return;
}
if ($context->contextlevel == CONTEXT_MODULE) {
// Delete all assignment submissions for the assignment associated with the context module.
$assignment = self::get_assignment_by_context($context);
if ($assignment != null) {
$DB->delete_records('assignment_submissions', ['assignment' => $assignment->id]);
// Delete all file uploads associated with the assignment submission for the specified context.
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_assignment', 'submission');
}
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist a list of contexts approved for deletion.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
// Only retrieve assignment submissions submitted by the user for deletion.
$assignmentsubmissionids = array_keys(self::get_assignment_submissions_by_contextlist($contextlist, $userid));
$DB->delete_records_list('assignment_submissions', 'id', $assignmentsubmissionids);
// Delete all file uploads associated with the assignment submission for the user's specified list of contexts.
$fs = get_file_storage();
foreach ($contextlist->get_contextids() as $contextid) {
foreach ($assignmentsubmissionids as $submissionid) {
$fs->delete_area_files($contextid, 'mod_assignment', 'submission', $submissionid);
}
}
}
// Start of helper functions.
/**
* Helper function to check if a user has marked assignment submissions for a given assignment.
*
* @param int $assignmentid The assignment ID to check if user has marked associated submissions.
* @param int $userid The user ID to check if user has marked associated submissions.
* @return bool If user has marked associated submissions returns true, otherwise false.
* @throws \dml_exception
*/
protected static function has_marked_assignment_submissions($assignmentid, $userid) {
global $DB;
$params = [
'assignment' => $assignmentid,
'teacher' => $userid
];
$sql = "SELECT count(s.id) as nomarked
FROM {assignment_submissions} s
WHERE s.assignment = :assignment
AND s.teacher = :teacher";
$results = $DB->get_record_sql($sql, $params);
return ($results->nomarked > 0) ? true : false;
}
/**
* Helper function to return assignment for a context module.
*
* @param object $context The context module object to return the assignment record by.
* @return mixed The assignment details or null record associated with the context module.
* @throws \dml_exception
*/
protected static function get_assignment_by_context($context) {
global $DB;
$params = [
'modulename' => 'assignment',
'contextmodule' => CONTEXT_MODULE,
'contextid' => $context->id
];
$sql = "SELECT a.id,
a.name,
a.intro,
a.assignmenttype,
a.grade,
a.timedue,
a.timeavailable,
a.timemodified
FROM {assignment} a
JOIN {course_modules} cm ON a.id = cm.instance
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
WHERE ctx.id = :contextid";
return $DB->get_record_sql($sql, $params);
}
/**
* Helper function to return assignment submissions submitted by / marked by a user and their contextlist.
*
* @param object $contextlist Object with the contexts related to a userid to retrieve assignment submissions by.
* @param int $userid The user ID to find assignment submissions that were submitted by.
* @param bool $teacher The teacher status to determine if marked assignment submissions should be returned.
* @return array Array of assignment submission details.
* @throws \coding_exception
* @throws \dml_exception
*/
protected static function get_assignment_submissions_by_contextlist($contextlist, $userid, $teacher = false) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$params = [
'contextmodule' => CONTEXT_MODULE,
'modulename' => 'assignment',
'userid' => $userid
];
$sql = "SELECT s.id as id,
s.assignment as assignment,
s.numfiles as numfiles,
s.data1 as data1,
s.data2 as data2,
s.grade as grade,
s.submissioncomment as submissioncomment,
s.teacher as teacher,
s.timemarked as timemarked,
s.timecreated as timecreated,
s.timemodified as timemodified
FROM {context} ctx
JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
JOIN {assignment} a ON cm.instance = a.id
JOIN {assignment_submissions} s ON s.assignment = a.id
WHERE (s.userid = :userid";
if ($teacher == true) {
$sql .= " OR s.teacher = :teacher";
$params['teacher'] = $userid;
}
$sql .= ")";
$sql .= " AND ctx.id {$contextsql}";
$params += $contextparams;
return $DB->get_records_sql($sql, $params);
}
/**
* Helper function to retrieve assignment submissions submitted by / marked by a user for a specific assignment.
*
* @param int $assignmentid The assignment ID to retrieve assignment submissions by.
* @param int $userid The user ID to retrieve assignment submissions submitted / marked by.
* @param bool $teacher The teacher status to determine if marked assignment submissions should be returned.
* @return array Array of assignment submissions details.
* @throws \dml_exception
*/
protected static function get_assignment_submissions_by_assignment($assignmentid, $userid, $teacher = false) {
global $DB;
$params = [
'assignment' => $assignmentid,
'userid' => $userid
];
$sql = "SELECT s.id as id,
s.assignment as assignment,
s.numfiles as numfiles,
s.data1 as data1,
s.data2 as data2,
s.grade as grade,
s.submissioncomment as submissioncomment,
s.teacher as teacher,
s.timemarked as timemarked,
s.timecreated as timecreated,
s.timemodified as timemodified,
s.userid as userid
FROM {assignment_submissions} s
WHERE s.assignment = :assignment
AND (s.userid = :userid";
if ($teacher == true) {
$sql .= " OR s.teacher = :teacher";
$params['teacher'] = $userid;
}
$sql .= ")";
return $DB->get_records_sql($sql, $params);
}
/**
* Helper function generate assignment output object for exporting.
*
* @param object $assignmentdata Object containing assignment data.
* @return object Formatted assignment output object for exporting.
*/
protected static function get_assignment_output($assignmentdata) {
$assignment = (object) [
'name' => $assignmentdata->name,
'intro' => $assignmentdata->intro,
'assignmenttype' => $assignmentdata->assignmenttype,
'grade' => $assignmentdata->grade,
'timemodified' => transform::datetime($assignmentdata->timemodified)
];
if ($assignmentdata->timeavailable != 0) {
$assignment->timeavailable = transform::datetime($assignmentdata->timeavailable);
}
if ($assignmentdata->timedue != 0) {
$assignment->timedue = transform::datetime($assignmentdata->timedue);
}
return $assignment;
}
/**
* Helper function generate assignment submission output object for exporting.
*
* @param object $submissiondata Object containing assignment submission data.
* @return object Formatted assignment submission output for exporting.
*/
protected static function get_assignment_submission_output($submissiondata) {
$submission = (object) [
'assignment' => $submissiondata->assignment,
'numfiles' => $submissiondata->numfiles,
'data1' => $submissiondata->data1,
'data2' => $submissiondata->data2,
'grade' => $submissiondata->grade,
'submissioncomment' => $submissiondata->submissioncomment,
'teacher' => transform::user($submissiondata->teacher)
];
if ($submissiondata->timecreated != 0) {
$submission->timecreated = transform::datetime($submissiondata->timecreated);
}
if ($submissiondata->timemarked != 0) {
$submission->timemarked = transform::datetime($submissiondata->timemarked);
}
if ($submissiondata->timemodified != 0) {
$submission->timemodified = transform::datetime($submissiondata->timemodified);
}
return $submission;
}
}

View File

@ -42,3 +42,21 @@ $string['pluginname'] = 'Assignment 2.2 (Disabled)';
$string['upgradenotification'] = 'This activity is based on an older assignment module.';
$string['viewassignmentupgradetool'] = 'View the assignment upgrade tool';
$string['pluginadministration'] = 'Assignment 2.2 (Disabled) administration';
$string['privacy:markedsubmissionspath'] = 'markedsubmissions';
$string['privacy:submissionpath'] = 'submission';
$string['privacy:metadata:assignmentfilter'] = 'Filter preference of assignment submissions.';
$string['privacy:metadata:assignmentperpage'] = 'Number of assignment submissions shown per page preference.';
$string['privacy:metadata:assignmentmailinfo'] = 'Mail info preference for assignment submissions.';
$string['privacy:metadata:assignmentquickgrade'] = 'Quick grading preference for assignment submissions.';
$string['privacy:metadata:assignment_submissions'] = 'Assignment submissions associated with an assignment.';
$string['privacy:metadata:assignment_submissions:userid'] = 'The user ID submitting the assignment submission.';
$string['privacy:metadata:assignment_submissions:timecreated'] = 'The creation date/time of the assignment submission.';
$string['privacy:metadata:assignment_submissions:timemodified'] = 'The modification date/time of the assignment submission.';
$string['privacy:metadata:assignment_submissions:numfiles'] = 'The maximum number of files allowed for the assignment submission.';
$string['privacy:metadata:assignment_submissions:data1'] = 'The onlinetext submitted for the assignment submission.';
$string['privacy:metadata:assignment_submissions:data2'] = '';
$string['privacy:metadata:assignment_submissions:grade'] = 'The grade value awarded for the assignment submission.';
$string['privacy:metadata:assignment_submissions:submissioncomment'] = 'The submission comment accompanying the assignment submission.';
$string['privacy:metadata:assignment_submissions:teacher'] = 'The teacher user ID grading the assignment submission.';
$string['privacy:metadata:assignment_submissions:timemarked'] = 'The marking date/time of the assignment submission.';
$string['privacy:metadata:assignment_submissions:mailed'] = 'The mailed notification status of the assignment submission.';

View File

@ -0,0 +1,606 @@
<?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/>.
/**
* Privacy test for the event monitor
*
* @package mod_assignment
* @category test
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../lib.php');
use \mod_assignment\privacy\provider;
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\writer;
use \core_privacy\tests\provider_testcase;
/**
* Privacy test for the event monitor
*
* @package mod_assignment
* @category test
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_assignment_privacy_testcase extends advanced_testcase {
/**
* @var int array Array of test student ids associated for Course 1.
*/
private $course1students = [];
/**
* @var int array Array of test student ids associated for Course 2.
*/
private $course2students = [];
/**
* Test for provider::get_contexts_for_userid().
*
* @throws coding_exception
*/
public function test_get_contexts_for_userid() {
global $DB;
$this->resetAfterTest(true);
$this->create_courses_and_assignments();
// Get Teacher 1 to test get_contexts_for_userid().
$teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
$contextids = provider::get_contexts_for_userid($teacher1->id);
// Verify there should be 4 contexts, as Teacher 1 has submitted tests and marked Assignments in Course 1 and 2.
$this->assertEquals(4, count($contextids->get_contextids()));
// Get Teacher 2 to test get_contexts_for_userid().
$teacher2 = $DB->get_record('user', ['username' => 'teacher2']);
$contextids = provider::get_contexts_for_userid($teacher2->id);
// Verify there should be 0 contexts, as teacher 2 has not marked any Assignments.
$this->assertEquals(0, count($contextids->get_contextids()));
// Get Student 1 to test get_contexts_for_userid().
$student1 = $DB->get_record('user', ['username' => 'student1']);
$contextids = provider::get_contexts_for_userid($student1->id);
// Verify there should be 2 contexts, as student 1 added submissions for both Assignments in Course 1.
$this->assertEquals(2, count($contextids->get_contextids()));
// Get Student 2 to test get_contexts_for_userid().
$student2 = $DB->get_record('user', ['username' => 'student2']);
$contextids = provider::get_contexts_for_userid($student2->id);
// Verify there should be 2 context, as student 2 added submissions for both Assignments in Course 2.
$this->assertEquals(2, count($contextids->get_contextids()));
}
/**
* Test for provider::export_user_data().
*
* @throws coding_exception
*/
public function test_export_user_data_teacher() {
global $DB;
$this->resetAfterTest(true);
$this->create_courses_and_assignments();
// Test Teacher 1 export_data_for_user() - marking assignment submissions for both Course 1 and 2.
$teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
$contextlist = provider::get_contexts_for_userid($teacher1->id);
$approvedcontextlist = new approved_contextlist($teacher1, 'mod_assignment', $contextlist->get_contextids());
// Verify Teacher 1 has four contexts.
$this->assertCount(4, $contextlist->get_contextids());
// Retrieve Assignment Submissions data for Teacher 1.
provider::export_user_data($approvedcontextlist);
$contexts = $contextlist->get_contexts();
// Context 1 - Course 1's Assignment 1 -- (onlinetext).
$writer = writer::with_context($contexts[0]);
$subcontexts = [
get_string('privacy:markedsubmissionspath', 'mod_assignment'),
transform::user($teacher1->id)
];
// Verify the test assignment submission from Teacher 1 exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals('<p>Course 1 - Ass 1: Teacher Test Submission</p>', $submission->data1);
foreach ($this->course1students as $student) {
$subcontexts = [
get_string('privacy:markedsubmissionspath', 'mod_assignment'),
transform::user($student->id)
];
// Verify the student assignment submissions exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 1 - Ass 1: " . $student->id . "</p>", $submission->data1);
}
// Context 2 - Course 1's Assignment 2 -- (single file upload).
$writer = writer::with_context($contexts[1]);
foreach ($this->course1students as $student) {
$subcontexts = [
get_string('privacy:markedsubmissionspath', 'mod_assignment'),
transform::user($student->id)
];
// Verify the student assignment submissions exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 1 - Ass 2: " . $student->id . "</p>", $submission->data1);
// Verify the student assignment submission file upload exists.
$submissionfiles = $writer->get_files($subcontexts);
$this->assertTrue(array_key_exists('Student' . $student->id . '-Course1-Ass2-(File 1 of 1)', $submissionfiles));
}
// Context 3 - Course 2's Assignment 1 -- (offline).
$writer = writer::with_context($contexts[2]);
foreach ($this->course2students as $student) {
$subcontexts = [
get_string('privacy:markedsubmissionspath', 'mod_assignment'),
transform::user($student->id)
];
// Verify the student assignment submissions exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 2 - Ass 1: " . $student->id . "</p>", $submission->data1);
}
// Context 4 - Course 2's Assignment 2 -- (multiple file upload).
$writer = writer::with_context($contexts[3]);
foreach ($this->course2students as $student) {
$subcontexts = [
get_string('privacy:markedsubmissionspath', 'mod_assignment'),
transform::user($student->id)
];
// Verify the student assignment submissions exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 2 - Ass 2: " . $student->id . "</p>", $submission->data1);
// Verify the student assignment submission file upload exists.
$submissionfiles = $writer->get_files($subcontexts);
$this->assertTrue(array_key_exists('Student' . $student->id . '-Course2-Ass2-(File 1 of 2)', $submissionfiles));
$this->assertTrue(array_key_exists('Student' . $student->id . '-Course2-Ass2-(File 2 of 2)', $submissionfiles));
}
}
/**
* Test for provider::export_user_data().
*
* @throws dml_exception
*/
public function test_export_user_data_student() {
global $DB;
$this->resetAfterTest(true);
$this->create_courses_and_assignments();
// Test Student 1 export_data_for_user() - added assignment submissions for both assignments in Course 1.
$student1 = $DB->get_record('user', ['username' => 'student1']);
$contextlist = provider::get_contexts_for_userid($student1->id);
$approvedcontextlist = new approved_contextlist($student1, 'mod_assignment', $contextlist->get_contextids());
// Retrieve Assignment Submissions data for Student 1.
provider::export_user_data($approvedcontextlist);
$contexts = $contextlist->get_contexts();
// Context 1 - Course 1's Assignment 1 -- (onlinetext).
$writer = writer::with_context($contexts[0]);
$subcontexts = [
get_string('privacy:submissionpath', 'mod_assignment')
];
// Verify the student assignment submissions exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 1 - Ass 1: " . $student1->id . "</p>", $submission->data1);
// Context 2 - Course 1's Assignment 2 -- (single file upload).
$writer = writer::with_context($contexts[1]);
$subcontexts = [
get_string('privacy:submissionpath', 'mod_assignment')
];
// Verify the student assignment submission exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 1 - Ass 2: " . $student1->id . "</p>", $submission->data1);
// Verify the student assignment submission file upload exists.
$submissionfiles = $writer->get_files($subcontexts);
$this->assertTrue(array_key_exists('Student' . $student1->id . '-Course1-Ass2-(File 1 of 1)', $submissionfiles));
// Test Student 2 export_data_for_user() - added assignment submissions for both assignments in Course 2.
$student2 = $DB->get_record('user', ['username' => 'student2']);
$contextlist = provider::get_contexts_for_userid($student2->id);
$approvedcontextlist = new approved_contextlist($student2, 'mod_assignment', $contextlist->get_contextids());
// Retrieve Assignment Submissions data for Student 2.
provider::export_user_data($approvedcontextlist);
$contexts = $contextlist->get_contexts();
// Context 1 - Course 2's Assignment 1 -- (offline).
$writer = writer::with_context($contexts[0]);
$subcontexts = [
get_string('privacy:submissionpath', 'mod_assignment')
];
// Verify the student assignment submissions exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 2 - Ass 1: " . $student2->id . "</p>", $submission->data1);
// Context 2 - Course 2's Assignment 2 -- (multiple file upload).
$writer = writer::with_context($contexts[1]);
$subcontexts = [
get_string('privacy:submissionpath', 'mod_assignment')
];
// Verify the student assignment submission exists.
$submission = $writer->get_data($subcontexts);
$this->assertEquals("<p>Course 2 - Ass 2: " . $student2->id . "</p>", $submission->data1);
// Verify the student assignment submission file upload exists.
$submissionfiles = $writer->get_files($subcontexts);
$this->assertTrue(array_key_exists('Student' . $student2->id . '-Course2-Ass2-(File 1 of 2)', $submissionfiles));
$this->assertTrue(array_key_exists('Student' . $student2->id . '-Course2-Ass2-(File 2 of 2)', $submissionfiles));
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*
* @throws dml_exception
*/
public function test_delete_data_for_all_users_in_context() {
global $DB;
$this->resetAfterTest(true);
$this->create_courses_and_assignments();
// Test teacher1 delete_data_for_all_users_in_context().
$teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
$contextlist = provider::get_contexts_for_userid($teacher1->id);
foreach ($contextlist as $context) {
provider::delete_data_for_all_users_in_context($context);
// Verify assignment submission(s) were deleted for the context.
$deleted = $this->get_assignment_submissions($context->id);
$this->assertCount(0, $deleted);
// Verify all the file submissions associated with the context for all users were deleted.
$files = $DB->get_records('files', ['component' => 'mod_assignment', 'filearea' => 'submission', 'contextid' => $context->id]);
$this->assertEquals(0, count($files));
}
}
/**
* Test for provider::delete_data_for_user().
*
* @throws dml_exception
*/
public function test_delete_data_for_user() {
global $DB;
$this->resetAfterTest(true);
$this->create_courses_and_assignments();
// Test Teacher 1 delete_data_for_user(), should only remove the 1 test submission added by Teacher 1.
// Should not remove any assignment submission records marked by the teacher.
$teacher1 = $DB->get_record('user', ['username' => 'teacher1']);
$contextlist = provider::get_contexts_for_userid($teacher1->id);
$approvedcontextlist = new approved_contextlist($teacher1, 'mod_assignment', $contextlist->get_contextids());
provider::delete_data_for_user($approvedcontextlist);
// Verify the submissions submitted by students still exists.
$markedsubmissions = $DB->get_records('assignment_submissions', ['teacher' => $teacher1->id]);
$this->assertCount(4, $markedsubmissions);
// Test student 1 delete_data_for_user().
$student1 = $DB->get_record('user', ['username' => 'student1']);
$contextlist = provider::get_contexts_for_userid($student1->id);
$approvedcontextlist = new approved_contextlist($student1, 'mod_assignment', $contextlist->get_contextids());
provider::delete_data_for_user($approvedcontextlist);
// Verify student 1's assignment submissions were deleted.
$assignmentsubmissions = $DB->get_records('assignment_submissions', ['userid' => $student1->id]);
$this->assertEquals(0, count($assignmentsubmissions));
// Verify student 1's file submissions were deleted.
foreach ($contextlist->get_contextids() as $contextid) {
$files = $DB->get_records('files', ['component' => 'mod_assignment', 'filearea' => 'submission', 'contextid' => $contextid]);
$this->assertEquals(0, count($files));
}
}
// Start of helper functions.
/**
* Helper function to setup Course, users, and assignments for testing.
*/
protected function create_courses_and_assignments() {
// Create Courses, Users, and Assignments.
$course1 = $this->getDataGenerator()->create_course(['shortname' => 'course1']);
$course2 = $this->getDataGenerator()->create_course(['shortname' => 'course2']);
$teacher1 = $this->getDataGenerator()->create_user(['username' => 'teacher1']);
$teacher2 = $this->getDataGenerator()->create_user(['username' => 'teacher2']);
$student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
$student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
$this->course1students = [
$student1
];
$this->course2students = [
$student2
];
$course1assignment1 = $this->getDataGenerator()->create_module('assignment',
[
'course' => $course1->id,
'name' => 'Course 1 - Assignment 1 (onlinetext)',
'assignmenttype' => 'onlinetext',
]
);
$course1assignment2 = $this->getDataGenerator()->create_module('assignment',
[
'course' => $course1->id,
'name' => 'Course 1 - Assignment 2 (single file upload)',
'assignmenttype' => 'uploadsingle',
]
);
$course2assignment1 = $this->getDataGenerator()->create_module('assignment',
[
'course' => $course2->id,
'name' => 'Course 2 - Assignment 1 (offline)',
'assignmenttype' => 'offline',
]
);
$course2assignment2 = $this->getDataGenerator()->create_module('assignment',
[
'course' => $course2->id,
'name' => 'Course 2 - Assignment 2 (multiple file upload)',
'assignmenttype' => 'upload',
]
);
// Teacher 1 add test assignment submission for Course 1 - Assignment 1.
$this->add_assignment_submission(
$course1assignment1,
$teacher1,
"Course 1 - Ass 1: Teacher Test Submission"
);
// Student 1 add assignment submissions for Course 1 - Assignment 1 and 2.
$this->add_assignment_submission(
$course1assignment1,
$student1,
"Course 1 - Ass 1: " . $student1->id
);
$this->add_file_assignment_submission(
$course1assignment2,
$student1,
"Course 1 - Ass 2: " . $student1->id,
'Student' . $student1->id . '-Course1-Ass2'
);
// Student 2 add assignment submissions for Course 2 - Assignment 1 and 2.
$this->add_assignment_submission(
$course2assignment1,
$student2,
"Course 2 - Ass 1: " . $student2->id
);
$this->add_file_assignment_submission(
$course2assignment2,
$student2,
"Course 2 - Ass 2: " . $student2->id,
'Student' . $student2->id . '-Course2-Ass2',
2
);
// Teacher 1 to mark assignment submissions for Course 1's Assignment 1 and 2.
$course1submissions = $this->get_course_assignment_submissions($course1->id);
foreach ($course1submissions as $submission) {
$this->mark_assignment_submission($submission->assignment, $submission->id, $teacher1, 49);
}
// Teacher 1 to mark assignment submissions for Course 2's Assignment 1 and 2.
$course2submissions = $this->get_course_assignment_submissions($course2->id);
foreach ($course2submissions as $submission) {
$this->mark_assignment_submission($submission->assignment, $submission->id, $teacher1, 50);
}
}
/**
* Helper function to add an assignment submission for testing.
*
* @param object $assignment Object containing assignment submission details to create for testing.
* @param object $user Object of the user making the assignment submission.
* @param string $submissiondata The onlintext string value of the assignment submission.
* @throws dml_exception
*/
protected function add_assignment_submission($assignment, $user, $submissiondata) {
global $DB;
$submission = (object) [
'assignment' => $assignment->id,
'userid' => $user->id,
'timecreated' => date('U'),
'data1' => '<p>' . $submissiondata . '</p>',
'submissioncomment' => 'My submission by ' . $user->username
];
return $DB->insert_record('assignment_submissions', $submission);
}
/**
* Helper function to add an assignment submission with file submissions for testing.
*
* @param object $assignment Object containing assignment submission details to create for testing.
* @param object $user Object of the user making the assignment submission.
* @param string $submissiondata The onlintext string value of the assignment submission.
* @param string $filename The filename of the file submission included with the assignment submission.
* @param int $numfiles The number of files included with the assignment submission.
* @throws dml_exception
* @throws file_exception
* @throws stored_file_creation_exception
*/
protected function add_file_assignment_submission($assignment, $user, $submissiondata, $filename, $numfiles = 1) {
global $CFG, $DB;
$submission = (object) [
'assignment' => $assignment->id,
'userid' => $user->id,
'timecreated' => date('U'),
'data1' => '<p>' . $submissiondata . '</p>',
'numfiles' => $numfiles,
'submissioncomment' => 'My submission by ' . $user->username
];
$submissionid = $DB->insert_record('assignment_submissions', $submission);
// Create a file submission with the test pdf.
$this->setUser($user->id);
$context = context_module::instance($assignment->cmid);
$fs = get_file_storage();
$sourcefile = $CFG->dirroot . '/mod/assign/feedback/editpdf/tests/fixtures/submission.pdf';
for ($f = 1; $f <= $numfiles; $f++) {
$pdfsubmission = (object)array(
'contextid' => $context->id,
'component' => 'mod_assignment',
'filearea' => 'submission',
'itemid' => $submissionid,
'filepath' => '/',
'filename' => $filename . "-(File $f of $numfiles)"
);
$fs->create_file_from_pathname($pdfsubmission, $sourcefile);
}
}
/**
* Helper function to retrieve the assignment submission records for a given course.
*
* @param int $courseid The course ID to get assignment submissions by.
* @return array Array of assignment submission details.
* @throws dml_exception
*/
protected function get_course_assignment_submissions($courseid) {
global $DB;
$sql = "SELECT s.id,
s.assignment,
s.userid,
s.timecreated,
s.timemodified,
s.numfiles,
s.data1,
s.data2,
s.grade,
s.submissioncomment,
s.format,
s.teacher,
s.timemarked,
s.mailed
FROM {assignment} a
JOIN {assignment_submissions} s ON s.assignment = a.id
WHERE a.course = :courseid";
$params = [
'courseid' => $courseid
];
return $DB->get_records_sql($sql, $params);
}
/**
* Helper function to update an assignment submission with grading details for a teacher.
*
* @param int $assignmentid The assignment ID to update assignment submissions with marking/graded details.
* @param int $submissionid The assignment submission ID to update with marking/grading details.
* @param int $teacher The teacher user ID to making the marking/grading details.
* @param int $gradedata The grade value set for the marking/grading details.
*/
protected function mark_assignment_submission($assignmentid, $submissionid, $teacher, $gradedata) {
global $DB;
$submission = (object) [
'id' => $submissionid,
'assignment' => $assignmentid,
'grade' => $gradedata,
'teacher' => $teacher->id,
'timemarked' => date('U')
];
return $DB->update_record('assignment_submissions', $submission);
}
/**
* Helper function to retrieve the assignment records for a given context.
*
* @param int $contextid The context module ID value to retrieve assignment IDs by.
* @return array Array of assignment IDs.
* @throws dml_exception
*/
protected function get_assignments($contextid) {
global $DB;
$sql = "SELECT a.id
FROM {assignment} a
JOIN {course_modules} cm ON a.id = cm.instance
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
WHERE ctx.id = :contextid";
$params = [
'modulename' => 'assignment',
'contextmodule' => CONTEXT_MODULE,
'contextid' => $contextid
];
return $DB->get_records_sql($sql, $params);
}
/**
* Helper function to retrieve the assignment submission records for a given context.
*
* @param int $contextid The context module ID value to retrieve assignment submission IDs by.
* @return array Array of assignment submission IDs.
* @throws dml_exception
*/
protected function get_assignment_submissions($contextid) {
global $DB;
$sql = "SELECT s.id
FROM {assignment_submissions} s
JOIN {assignment} a ON a.id = s.assignment
JOIN {course_modules} cm ON a.id = cm.instance
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
WHERE ctx.id = :contextid";
$params = [
'modulename' => 'assignment',
'contextmodule' => CONTEXT_MODULE,
'contextid' => $contextid
];
return $DB->get_records_sql($sql, $params);
}
}