/** @var object If set then only export data related directly to this user. */
protected $user;
+ /** @var array The user IDs of the users that will be affected. */
+ protected $userids;
+
+ /** @var array The submissions related to the users added. */
+ protected $submissions = [];
+
+ /** @var array The grades related to the users added. */
+ protected $grades = [];
+
/** @var assign The assign object */
protected $assign;
$this->assign = $assign;
}
+ /**
+ * Method for adding an array of user IDs. This will do a query to populate the submissions and grades
+ * for these users.
+ *
+ * @param array $userids User IDs to do something with.
+ */
+ public function set_userids(array $userids) {
+ $this->userids = $userids;
+ }
+
/**
* Getter for this attribute.
*
public function get_assign() {
return $this->assign;
}
+
+ /**
+ * A method to conveniently fetch the assign id.
+ *
+ * @return int The assign id.
+ */
+ public function get_assignid() {
+ return $this->assign->get_instance()->id;
+ }
+
+ /**
+ * Get all of the user IDs
+ *
+ * @return array User IDs
+ */
+ public function get_userids() {
+ return $this->userids;
+ }
+
+ /**
+ * Returns all of the submission IDs
+ *
+ * @return array submission IDs
+ */
+ public function get_submissionids() {
+ return array_keys($this->submissions);
+ }
+
+ /**
+ * Returns the submissions related to the user IDs
+ *
+ * @return array User submissions.
+ */
+ public function get_submissions() {
+ return $this->submissions;
+ }
+
+ /**
+ * Returns the grade IDs related to the user IDs
+ *
+ * @return array User grade IDs.
+ */
+ public function get_gradeids() {
+ return array_keys($this->grades);
+ }
+
+ /**
+ * Returns the grades related to the user IDs
+ *
+ * @return array User grades.
+ */
+ public function get_grades() {
+ return $this->grades;
+ }
+
+ /**
+ * Fetches all of the submissions and grades related to the User IDs provided. Use get_grades, get_submissions etc to
+ * retrieve this information.
+ */
+ public function populate_submissions_and_grades() {
+ global $DB;
+
+ if (empty($this->get_userids())) {
+ throw new \coding_exception('Please use set_userids() before calling this method.');
+ }
+
+ list($sql, $params) = $DB->get_in_or_equal($this->get_userids(), SQL_PARAMS_NAMED);
+ $params['assign'] = $this->get_assign()->get_instance()->id;
+ $this->submissions = $DB->get_records_select('assign_submission', "assignment = :assign AND userid $sql", $params);
+ $this->grades = $DB->get_records_select('assign_grades', "assignment = :assign AND userid $sql", $params);
+ }
}
require_once($CFG->dirroot . '/mod/assign/locallib.php');
use \core_privacy\local\metadata\collection;
-use \core_privacy\local\metadata\provider as metadataprovider;
use \core_privacy\local\request\contextlist;
-use \core_privacy\local\request\plugin\provider as pluginprovider;
-use \core_privacy\local\request\user_preference_provider as preference_provider;
use \core_privacy\local\request\writer;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\helper;
+use \core_privacy\local\request\userlist;
+use \core_privacy\local\request\approved_userlist;
use \core_privacy\manager;
/**
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class provider implements metadataprovider, pluginprovider, preference_provider {
+class provider implements
+ \core_privacy\local\metadata\provider,
+ \core_privacy\local\request\plugin\provider,
+ \core_privacy\local\request\user_preference_provider,
+ \core_privacy\local\request\core_userlist_provider {
/** Interface for all assign submission sub-plugins. */
const ASSIGNSUBMISSION_INTERFACE = 'mod_assign\privacy\assignsubmission_provider';
+ /** Interface for all assign submission sub-plugins. This allows for deletion of users with a context. */
+ const ASSIGNSUBMISSION_USER_INTERFACE = 'mod_assign\privacy\assignsubmission_user_provider';
+
+ /** Interface for all assign feedback sub-plugins. This allows for deletion of users with a context. */
+ const ASSIGNFEEDBACK_USER_INTERFACE = 'mod_assign\privacy\assignfeedback_user_provider';
+
/** Interface for all assign feedback sub-plugins. */
const ASSIGNFEEDBACK_INTERFACE = 'mod_assign\privacy\assignfeedback_provider';
return $contextlist;
}
+ /**
+ * Get the list of contexts that contain user information for the specified user.
+ *
+ * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
+ */
+ public static function get_users_in_context(userlist $userlist) {
+
+ $context = $userlist->get_context();
+ if ($context->contextlevel != CONTEXT_MODULE) {
+ return;
+ }
+
+ $params = [
+ 'modulename' => 'assign',
+ 'contextid' => $context->id,
+ 'contextlevel' => CONTEXT_MODULE
+ ];
+
+ $sql = "SELECT g.userid, g.grader
+ FROM {context} ctx
+ JOIN {course_modules} cm ON cm.id = ctx.instanceid
+ JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+ JOIN {assign} a ON a.id = cm.instance
+ JOIN {assign_grades} g ON a.id = g.assignment
+ WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+ $userlist->add_from_sql('userid', $sql, $params);
+ $userlist->add_from_sql('grader', $sql, $params);
+
+ $sql = "SELECT o.userid
+ FROM {context} ctx
+ JOIN {course_modules} cm ON cm.id = ctx.instanceid
+ JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+ JOIN {assign} a ON a.id = cm.instance
+ JOIN {assign_overrides} o ON a.id = o.assignid
+ WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+ $userlist->add_from_sql('userid', $sql, $params);
+
+ $sql = "SELECT s.userid
+ FROM {context} ctx
+ JOIN {course_modules} cm ON cm.id = ctx.instanceid
+ JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+ JOIN {assign} a ON a.id = cm.instance
+ JOIN {assign_submission} s ON a.id = s.assignment
+ WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+ $userlist->add_from_sql('userid', $sql, $params);
+
+ $sql = "SELECT uf.userid
+ FROM {context} ctx
+ JOIN {course_modules} cm ON cm.id = ctx.instanceid
+ JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+ JOIN {assign} a ON a.id = cm.instance
+ JOIN {assign_user_flags} uf ON a.id = uf.assignment
+ WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+ $userlist->add_from_sql('userid', $sql, $params);
+
+ $sql = "SELECT um.userid
+ FROM {context} ctx
+ JOIN {course_modules} cm ON cm.id = ctx.instanceid
+ JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
+ JOIN {assign} a ON a.id = cm.instance
+ JOIN {assign_user_mapping} um ON a.id = um.assignment
+ WHERE ctx.id = :contextid AND ctx.contextlevel = :contextlevel";
+ $userlist->add_from_sql('userid', $sql, $params);
+
+ manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE,
+ 'get_userids_from_context', [$userlist]);
+ manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE,
+ 'get_userids_from_context', [$userlist]);
+ }
+
/**
* Write out the user data filtered by contexts.
*
}
// Time to roll my own method for deleting overrides.
- static::delete_user_overrides($assign);
+ static::delete_overrides_for_users($assign);
$DB->delete_records('assign_submission', ['assignment' => $assign->get_instance()->id]);
$DB->delete_records('assign_user_flags', ['assignment' => $assign->get_instance()->id]);
$DB->delete_records('assign_user_mapping', ['assignment' => $assign->get_instance()->id]);
}
}
- static::delete_user_overrides($assign, $user);
+ static::delete_overrides_for_users($assign, [$user->id]);
$DB->delete_records('assign_user_flags', ['assignment' => $assignid, 'userid' => $user->id]);
$DB->delete_records('assign_user_mapping', ['assignment' => $assignid, 'userid' => $user->id]);
$DB->delete_records('assign_grades', ['assignment' => $assignid, 'userid' => $user->id]);
}
/**
- * Deletes assignment overrides.
+ * Delete multiple users within a single context.
*
- * @param \assign $assign The assignment object
- * @param \stdClass $user The user object if we are deleting only the overrides for one user.
+ * @param approved_userlist $userlist The approved context and user information to delete information for.
*/
- protected static function delete_user_overrides(\assign $assign, \stdClass $user = null) {
+ public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
+ $context = $userlist->get_context();
+ if ($context->contextlevel != CONTEXT_MODULE) {
+ return;
+ }
+
+ $userids = $userlist->get_userids();
+
+ $assign = new \assign($context, null, null);
$assignid = $assign->get_instance()->id;
- $params = (isset($user)) ? ['assignid' => $assignid, 'userid' => $user->id] : ['assignid' => $assignid];
+ $requestdata = new assign_plugin_request_data($context, $assign);
+ $requestdata->set_userids($userids);
+ $requestdata->populate_submissions_and_grades();
+ manager::plugintype_class_callback('assignsubmission', self::ASSIGNSUBMISSION_USER_INTERFACE, 'delete_submissions',
+ [$requestdata]);
+ manager::plugintype_class_callback('assignfeedback', self::ASSIGNFEEDBACK_USER_INTERFACE, 'delete_feedback_for_grades',
+ [$requestdata]);
+
+ // Update this function to delete advanced grading information.
+ $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
+ $controller = $gradingmanager->get_active_controller();
+ if (isset($controller)) {
+ $gradeids = $requestdata->get_gradeids();
+ // Careful here, if no gradeids are provided then all data is deleted for the context.
+ if (!empty($gradeids)) {
+ \core_grading\privacy\provider::delete_data_for_instances($context, $gradeids);
+ }
+ }
- $overrides = $DB->get_records('assign_overrides', $params);
- if (!empty($overrides)) {
- foreach ($overrides as $override) {
+ static::delete_overrides_for_users($assign, $userids);
+ list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+ $params['assignment'] = $assignid;
+ $DB->delete_records_select('assign_user_flags', "assignment = :assignment AND userid $sql", $params);
+ $DB->delete_records_select('assign_user_mapping', "assignment = :assignment AND userid $sql", $params);
+ $DB->delete_records_select('assign_grades', "assignment = :assignment AND userid $sql", $params);
+ $DB->delete_records_select('assign_submission', "assignment = :assignment AND userid $sql", $params);
+ }
- // First delete calendar events associated with this override.
- $conditions = ['modulename' => 'assign', 'instance' => $assignid];
- if (isset($user)) {
- $conditions['userid'] = $user->id;
- }
- $DB->delete_records('event', $conditions);
+ /**
+ * Deletes assignment overrides in bulk
+ *
+ * @param \assign $assign The assignment object
+ * @param array $userids An array of user IDs
+ */
+ protected static function delete_overrides_for_users(\assign $assign, array $userids = []) {
+ global $DB;
+ $assignid = $assign->get_instance()->id;
- // Next delete the overrides.
- $DB->delete_records('assign_overrides', ['id' => $override->id]);
+ $usersql = '';
+ $params = ['assignid' => $assignid];
+ if (!empty($userids)) {
+ list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+ $params = array_merge($params, $userparams);
+ $overrides = $DB->get_records_select('assign_overrides', "assignid = :assignid AND userid $usersql", $params);
+ } else {
+ $overrides = $DB->get_records('assign_overrides', $params);
+ }
+ if (!empty($overrides)) {
+ $params = ['modulename' => 'assign', 'instance' => $assignid];
+ if (!empty($userids)) {
+ $params = array_merge($params, $userparams);
+ $DB->delete_records_select('event', "modulename = :modulename AND instance = :instance AND userid $usersql",
+ $params);
+ // Setting up for the next query.
+ $params = $userparams;
+ $usersql = "AND userid $usersql";
+ } else {
+ $DB->delete_records('event', $params);
+ // Setting up for the next query.
+ $params = [];
}
+ list($overridesql, $overrideparams) = $DB->get_in_or_equal(array_keys($overrides), SQL_PARAMS_NAMED);
+ $params = array_merge($params, $overrideparams);
+ $DB->delete_records_select('assign_overrides', "id $overridesql $usersql", $params);
}
}
$this->assertEmpty(array_diff($usercontextids, $contextlist->get_contextids()));
}
+ /**
+ * Test returning a list of user IDs related to a context (assign).
+ */
+ public function test_get_users_in_context() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $course = $this->getDataGenerator()->create_course();
+
+ // Only made a comment on a submission.
+ $user1 = $this->getDataGenerator()->create_user();
+ // User 2 only has information about an activity override.
+ $user2 = $this->getDataGenerator()->create_user();
+ // User 3 made a submission.
+ $user3 = $this->getDataGenerator()->create_user();
+ // User 4 makes a submission and it is marked by the teacher.
+ $user4 = $this->getDataGenerator()->create_user();
+ // Grading and providing feedback as a teacher.
+ $user5 = $this->getDataGenerator()->create_user();
+ // This user has no entries and should not show up.
+ $user6 = $this->getDataGenerator()->create_user();
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user4->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user5->id, $course->id, 'editingteacher');
+ $this->getDataGenerator()->enrol_user($user6->id, $course->id, 'student');
+
+ $assign1 = $this->create_instance(['course' => $course,
+ 'assignsubmission_onlinetext_enabled' => true,
+ 'assignfeedback_comments_enabled' => true]);
+ $assign2 = $this->create_instance(['course' => $course]);
+
+ $context = $assign1->get_context();
+
+ // Jam an entry in the comments table for user 1.
+ $comment = (object) [
+ 'contextid' => $context->id,
+ 'component' => 'assignsubmission_comments',
+ 'commentarea' => 'submission_comments',
+ 'itemid' => 5,
+ 'content' => 'A comment by user 1',
+ 'format' => 0,
+ 'userid' => $user1->id,
+ 'timecreated' => time()
+ ];
+ $DB->insert_record('comments', $comment);
+
+ $this->setUser($user5); // Set the user to the teacher.
+
+ $overridedata = new \stdClass();
+ $overridedata->assignid = $assign1->get_instance()->id;
+ $overridedata->userid = $user2->id;
+ $overridedata->duedate = time();
+ $overridedata->allowsubmissionsfromdate = time();
+ $overridedata->cutoffdate = time();
+ $DB->insert_record('assign_overrides', $overridedata);
+
+ $submissiontext = 'My first submission';
+ $submission = $this->create_submission($assign1, $user3, $submissiontext);
+
+ $submissiontext = 'My first submission';
+ $submission = $this->create_submission($assign1, $user4, $submissiontext);
+
+ $this->setUser($user5);
+
+ $grade = '72.00';
+ $teachercommenttext = 'This is better. Thanks.';
+ $data = new \stdClass();
+ $data->attemptnumber = 1;
+ $data->grade = $grade;
+ $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
+
+ // Give the submission a grade.
+ $assign1->save_grade($user4->id, $data);
+
+ $userlist = new \core_privacy\local\request\userlist($context, 'assign');
+ provider::get_users_in_context($userlist);
+ $userids = $userlist->get_userids();
+ $this->assertTrue(in_array($user1->id, $userids));
+ $this->assertTrue(in_array($user2->id, $userids));
+ $this->assertTrue(in_array($user3->id, $userids));
+ $this->assertTrue(in_array($user4->id, $userids));
+ $this->assertTrue(in_array($user5->id, $userids));
+ $this->assertFalse(in_array($user6->id, $userids));
+ }
+
/**
* Test that a student with multiple submissions and grades is returned with the correct data.
*/
// The remaining event should be for user 1.
$this->assertEquals($user1->id, $record->userid);
}
+
+ /**
+ * A test for deleting all user data for a bunch of users.
+ */
+ public function test_delete_data_for_users() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $course = $this->getDataGenerator()->create_course();
+
+ // Only made a comment on a submission.
+ $user1 = $this->getDataGenerator()->create_user();
+ // User 2 only has information about an activity override.
+ $user2 = $this->getDataGenerator()->create_user();
+ // User 3 made a submission.
+ $user3 = $this->getDataGenerator()->create_user();
+ // User 4 makes a submission and it is marked by the teacher.
+ $user4 = $this->getDataGenerator()->create_user();
+ // Grading and providing feedback as a teacher.
+ $user5 = $this->getDataGenerator()->create_user();
+ // This user has entries in assignment 2 and should not have their data deleted.
+ $user6 = $this->getDataGenerator()->create_user();
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user4->id, $course->id, 'student');
+ $this->getDataGenerator()->enrol_user($user5->id, $course->id, 'editingteacher');
+ $this->getDataGenerator()->enrol_user($user6->id, $course->id, 'student');
+
+ $assign1 = $this->create_instance(['course' => $course,
+ 'assignsubmission_onlinetext_enabled' => true,
+ 'assignfeedback_comments_enabled' => true]);
+ $assign2 = $this->create_instance(['course' => $course,
+ 'assignsubmission_onlinetext_enabled' => true,
+ 'assignfeedback_comments_enabled' => true]);
+
+ $context = $assign1->get_context();
+
+ // Jam an entry in the comments table for user 1.
+ $comment = (object) [
+ 'contextid' => $context->id,
+ 'component' => 'assignsubmission_comments',
+ 'commentarea' => 'submission_comments',
+ 'itemid' => 5,
+ 'content' => 'A comment by user 1',
+ 'format' => 0,
+ 'userid' => $user1->id,
+ 'timecreated' => time()
+ ];
+ $DB->insert_record('comments', $comment);
+
+ $this->setUser($user5); // Set the user to the teacher.
+
+ $overridedata = new \stdClass();
+ $overridedata->assignid = $assign1->get_instance()->id;
+ $overridedata->userid = $user2->id;
+ $overridedata->duedate = time();
+ $overridedata->allowsubmissionsfromdate = time();
+ $overridedata->cutoffdate = time();
+ $DB->insert_record('assign_overrides', $overridedata);
+
+ $submissiontext = 'My first submission';
+ $submission = $this->create_submission($assign1, $user3, $submissiontext);
+
+ $submissiontext = 'My first submission';
+ $submission = $this->create_submission($assign1, $user4, $submissiontext);
+
+ $submissiontext = 'My first submission';
+ $submission = $this->create_submission($assign2, $user6, $submissiontext);
+
+ $this->setUser($user5);
+
+ $grade = '72.00';
+ $teachercommenttext = 'This is better. Thanks.';
+ $data = new \stdClass();
+ $data->attemptnumber = 1;
+ $data->grade = $grade;
+ $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
+
+ // Give the submission a grade.
+ $assign1->save_grade($user4->id, $data);
+
+ $this->setUser($user5);
+
+ $grade = '81.00';
+ $teachercommenttext = 'This is nice.';
+ $data = new \stdClass();
+ $data->attemptnumber = 1;
+ $data->grade = $grade;
+ $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
+
+ // Give the submission a grade.
+ $assign2->save_grade($user6->id, $data);
+
+ // Check data is in place.
+ $data = $DB->get_records('assign_submission');
+ // We should have one entry for user 3 and two entries each for user 4 and 6.
+ $this->assertCount(5, $data);
+ $usercounts = [
+ $user3->id => 0,
+ $user4->id => 0,
+ $user6->id => 0
+ ];
+ foreach ($data as $datum) {
+ $usercounts[$datum->userid]++;
+ }
+ $this->assertEquals(1, $usercounts[$user3->id]);
+ $this->assertEquals(2, $usercounts[$user4->id]);
+ $this->assertEquals(2, $usercounts[$user6->id]);
+
+ $data = $DB->get_records('assign_grades');
+ // Two entries in assign_grades, one for each grade given.
+ $this->assertCount(2, $data);
+
+ $data = $DB->get_records('assign_overrides');
+ $this->assertCount(1, $data);
+
+ $data = $DB->get_records('comments');
+ $this->assertCount(1, $data);
+
+ $userlist = new \core_privacy\local\request\approved_userlist($context, 'assign', [$user1->id, $user2->id]);
+ provider::delete_data_for_users($userlist);
+
+ $data = $DB->get_records('assign_overrides');
+ $this->assertEmpty($data);
+
+ $data = $DB->get_records('comments');
+ $this->assertEmpty($data);
+
+ $data = $DB->get_records('assign_submission');
+ // No change here.
+ $this->assertCount(5, $data);
+
+ $userlist = new \core_privacy\local\request\approved_userlist($context, 'assign', [$user3->id, $user5->id]);
+ provider::delete_data_for_users($userlist);
+
+ $data = $DB->get_records('assign_submission');
+ // Only the record for user 3 has been deleted.
+ $this->assertCount(4, $data);
+
+ $data = $DB->get_records('assign_grades');
+ // Grades should be unchanged.
+ $this->assertCount(2, $data);
+ }
}