diff --git a/mod/assign/classes/privacy/assignsubmission_user_provider.php b/mod/assign/classes/privacy/assignsubmission_user_provider.php new file mode 100644 index 00000000000..1a9331cd0e9 --- /dev/null +++ b/mod/assign/classes/privacy/assignsubmission_user_provider.php @@ -0,0 +1,54 @@ +. + +/** + * This file contains the assignsubmission_user_provider interface. + * + * Assignment Sub plugins should implement this if they store personal information and can retrieve a userid. + * + * @package mod_assign + * @copyright 2018 Adrian Greeve + * + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assign\privacy; + +use core_privacy\local\request\userlist; + +defined('MOODLE_INTERNAL') || die(); + +interface assignsubmission_user_provider extends \core_privacy\local\request\plugin\subplugin_provider { + + /** + * If you have tables that contain userids and you can generate entries in your tables without creating an + * entry in the assign_submission table then please fill in this method. + * + * @param userlist $userlist The userlist object + */ + public static function get_userids_from_context(userlist $userlist); + + /** + * Deletes all submissions for the submission ids / userids provided in a context. + * assign_plugin_request_data contains: + * - context + * - assign object + * - submission ids (pluginids) + * - user ids + * @param assign_plugin_request_data $deletedata A class that contains the relevant information required for deletion. + */ + public static function delete_submissions(assign_plugin_request_data $deletedata); + +} \ No newline at end of file diff --git a/mod/assign/submission/comments/classes/privacy/provider.php b/mod/assign/submission/comments/classes/privacy/provider.php index 6f95fd2487d..bc7507bd003 100644 --- a/mod/assign/submission/comments/classes/privacy/provider.php +++ b/mod/assign/submission/comments/classes/privacy/provider.php @@ -41,7 +41,9 @@ use \mod_assign\privacy\assign_plugin_request_data; * @copyright 2018 Adrian Greeve * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements metadataprovider, \mod_assign\privacy\assignsubmission_provider { +class provider implements metadataprovider, + \mod_assign\privacy\assignsubmission_provider, + \mod_assign\privacy\assignsubmission_user_provider { /** * Return meta data about this plugin. @@ -89,6 +91,28 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission $useridlist->add_from_sql($sql, $params); } + /** + * If you have tables that contain userids and you can generate entries in your tables without creating an + * entry in the assign_submission table then please fill in this method. + * + * @param \core_privacy\local\request\userlist $userlist The userlist object + */ + public static function get_userids_from_context(\core_privacy\local\request\userlist $userlist) { + $context = $userlist->get_context(); + if ($context->contextlevel != CONTEXT_MODULE) { + return; + } + $sql = "SELECT userid + FROM {comments} + WHERE contextid = :contextid + AND component = :component + AND commentarea = :commentarea"; + $userlist->add_from_sql('userid', $sql, [ + 'contextid' => $context->id, + 'component' => 'assignsubmission_comments', + 'commentarea' => 'submission_comments']); + } + /** * Export all user data for this plugin. * @@ -128,4 +152,20 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission [$exportdata->get_context()->id]); comments_provider::delete_comments_for_user($contextlist, 'assignsubmission_comments', 'submission_comments'); } + + /** + * Deletes all submissions for the submission ids / userids provided in a context. + * assign_plugin_request_data contains: + * - context + * - assign object + * - submission ids (pluginids) + * - user ids + * @param assign_plugin_request_data $deletedata A class that contains the relevant information required for deletion. + */ + public static function delete_submissions(assign_plugin_request_data $deletedata) { + $userlist = new \core_privacy\local\request\approved_userlist($deletedata->get_context(), 'assignsubmission_comments', + $deletedata->get_userids()); + comments_provider::delete_comments_for_users($userlist, 'assignsubmission_comments', 'submission_comments'); + } + } diff --git a/mod/assign/submission/comments/tests/privacy_test.php b/mod/assign/submission/comments/tests/privacy_test.php index e5917e66d35..c02e7959abf 100644 --- a/mod/assign/submission/comments/tests/privacy_test.php +++ b/mod/assign/submission/comments/tests/privacy_test.php @@ -134,6 +134,51 @@ class assignsubmission_comments_privacy_testcase extends \mod_assign\tests\mod_a $this->assertEquals($user1->id, $useridlist->get_userids()[0]->id); } + /** + * Test returning users related to a given context. + */ + public function test_get_userids_from_context() { + // Get a bunch of users making comments. + // Some in one context some in another. + $this->resetAfterTest(); + $course = $this->getDataGenerator()->create_course(); + // Only in first context. + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + // First and second context. + $user3 = $this->getDataGenerator()->create_user(); + // Second context only. + $user4 = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student'); + $assign1 = $this->create_instance(['course' => $course]); + $assign2 = $this->create_instance(['course' => $course]); + + $assigncontext1 = $assign1->get_context(); + $assigncontext2 = $assign2->get_context(); + + $user1comment = 'Comment from user 1'; + list($plugin, $submission, $comment) = $this->create_comment_submission($assign1, $user1, $user1comment); + $user2comment = 'From user 2'; + $this->setUser($user2); + $comment->add($user2comment); + $user3comment = 'User 3 comment'; + $this->setUser($user3); + $comment->add($user3comment); + $user4comment = 'Comment from user 4'; + list($plugin, $submission, $comment) = $this->create_comment_submission($assign2, $user4, $user4comment); + $user3secondcomment = 'Comment on user 4 post.'; + $this->setUser($user3); + $comment->add($user3comment); + + $userlist = new \core_privacy\local\request\userlist($assigncontext1, 'assignsubmission_comments'); + \assignsubmission_comments\privacy\provider::get_userids_from_context($userlist); + $userids = $userlist->get_userids(); + $this->assertCount(3, $userids); + // Two is the key for user 3. + $this->assertEquals(2, array_search($user3->id, $userids)); + $this->assertFalse(array_search($user4->id, $userids)); + } + /** * Test that comments are exported for a user. */ @@ -259,4 +304,55 @@ class assignsubmission_comments_privacy_testcase extends \mod_assign\tests\mod_a $this->assertNotEquals($user1->id, $result->userid); } } + + /** + * Test deletion of all submissions for a context works. + */ + public function test_delete_submissions() { + global $DB; + // Get a bunch of users making comments. + // Some in one context some in another. + $this->resetAfterTest(); + $course = $this->getDataGenerator()->create_course(); + // Only in first context. + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + // First and second context. + $user3 = $this->getDataGenerator()->create_user(); + // Second context only. + $user4 = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student'); + $assign1 = $this->create_instance(['course' => $course]); + $assign2 = $this->create_instance(['course' => $course]); + + $assigncontext1 = $assign1->get_context(); + $assigncontext2 = $assign2->get_context(); + + $user1comment = 'Comment from user 1'; + list($plugin, $submission, $comment) = $this->create_comment_submission($assign1, $user1, $user1comment); + $user2comment = 'From user 2'; + $this->setUser($user2); + $comment->add($user2comment); + $user3comment = 'User 3 comment'; + $this->setUser($user3); + $comment->add($user3comment); + $user4comment = 'Comment from user 4'; + list($plugin, $submission, $comment) = $this->create_comment_submission($assign2, $user4, $user4comment); + $user3secondcomment = 'Comment on user 4 post.'; + $this->setUser($user3); + $comment->add($user3comment); + + // There should be three entries. One for the first three users. + $results = $DB->get_records('comments', ['contextid' => $assigncontext1->id]); + $this->assertCount(3, $results); + + $deletedata = new \mod_assign\privacy\assign_plugin_request_data($assigncontext1, $assign1); + $deletedata->set_userids([$user1->id, $user3->id]); + \assignsubmission_comments\privacy\provider::delete_submissions($deletedata); + + // We should be left with just a comment from user 2. + $results = $DB->get_records('comments', ['contextid' => $assigncontext1->id]); + $this->assertCount(1, $results); + $this->assertEquals($user2comment, current($results)->content); + } } diff --git a/mod/assign/submission/file/classes/privacy/provider.php b/mod/assign/submission/file/classes/privacy/provider.php index a10e67a281b..f02f6dc70ab 100644 --- a/mod/assign/submission/file/classes/privacy/provider.php +++ b/mod/assign/submission/file/classes/privacy/provider.php @@ -29,7 +29,6 @@ defined('MOODLE_INTERNAL') || die(); 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\writer; use \core_privacy\local\request\contextlist; use \mod_assign\privacy\assign_plugin_request_data; @@ -41,7 +40,10 @@ use \mod_assign\privacy\assign_plugin_request_data; * @copyright 2018 Adrian Greeve * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements metadataprovider, \mod_assign\privacy\assignsubmission_provider { +class provider implements + \core_privacy\local\metadata\provider, + \mod_assign\privacy\assignsubmission_provider, + \mod_assign\privacy\assignsubmission_user_provider { /** * Return meta data about this plugin. @@ -73,6 +75,16 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission // No need. } + /** + * If you have tables that contain userids and you can generate entries in your tables without creating an + * entry in the assign_submission table then please fill in this method. + * + * @param userlist $userlist The userlist object + */ + public static function get_userids_from_context(\core_privacy\local\request\userlist $userlist) { + // Not required. + } + /** * Export all user data for this plugin. * @@ -122,7 +134,7 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission } /** - * A call to this method should delete user data (where practicle) using the userid and submission. + * A call to this method should delete user data (where practical) using the userid and submission. * * @param assign_plugin_request_data $deletedata Details about the user and context to focus the deletion. */ @@ -137,7 +149,32 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission $fs->delete_area_files($deletedata->get_context()->id, 'assignsubmission_file', ASSIGNSUBMISSION_FILE_FILEAREA, $submissionid); - $DB->delete_records('assignsubmission_file', ['assignment' => $deletedata->get_assign()->get_instance()->id, - 'submission' => $submissionid]); + $DB->delete_records('assignsubmission_file', ['assignment' => $deletedata->get_assignid(), 'submission' => $submissionid]); + } + + /** + * Deletes all submissions for the submission ids / userids provided in a context. + * assign_plugin_request_data contains: + * - context + * - assign object + * - submission ids (pluginids) + * - user ids + * @param assign_plugin_request_data $deletedata A class that contains the relevant information required for deletion. + */ + public static function delete_submissions(assign_plugin_request_data $deletedata) { + global $DB; + + \core_plagiarism\privacy\provider::delete_plagiarism_for_users($deletedata->get_userids(), $deletedata->get_context()); + + if (empty($deletedata->get_submissionids())) { + return; + } + $fs = get_file_storage(); + list($sql, $params) = $DB->get_in_or_equal($deletedata->get_submissionids(), SQL_PARAMS_NAMED); + $fs->delete_area_files_select($deletedata->get_context()->id, 'assignsubmission_file', ASSIGNSUBMISSION_FILE_FILEAREA, + $sql, $params); + + $params['assignid'] = $deletedata->get_assignid(); + $DB->delete_records_select('assignsubmission_file', "assignment = :assignid AND submission $sql", $params); } } diff --git a/mod/assign/submission/file/tests/privacy_test.php b/mod/assign/submission/file/tests/privacy_test.php index 61f68412ad7..99dda2620f9 100644 --- a/mod/assign/submission/file/tests/privacy_test.php +++ b/mod/assign/submission/file/tests/privacy_test.php @@ -172,4 +172,77 @@ class assignsubmission_file_privacy_testcase extends \mod_assign\tests\mod_assig // There should be files here. $this->assertFalse($plugin2->is_empty($submission2)); } + + /** + * Test deletion of bulk submissions for a context. + */ + public function test_delete_submissions() { + global $DB; + + $this->resetAfterTest(); + // Create course, assignment, submission, and then a feedback comment. + $course = $this->getDataGenerator()->create_course(); + // Student. + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $user4 = $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'); + + $assign1 = $this->create_instance(['course' => $course]); + $assign2 = $this->create_instance(['course' => $course]); + + $context1 = $assign1->get_context(); + $context2 = $assign2->get_context(); + + $student1filename = 'user1file.pdf'; + list($plugin1, $submission1) = $this->create_file_submission($assign1, $user1, $student1filename); + $student2filename = 'user2file.pdf'; + list($plugin2, $submission2) = $this->create_file_submission($assign1, $user2, $student2filename); + $student3filename = 'user3file.pdf'; + list($plugin3, $submission3) = $this->create_file_submission($assign1, $user3, $student3filename); + $student4filename = 'user4file.pdf'; + list($plugin4, $submission4) = $this->create_file_submission($assign2, $user4, $student4filename); + $student5filename = 'user5file.pdf'; + list($plugin5, $submission5) = $this->create_file_submission($assign2, $user3, $student5filename); + + $submissionids = [ + $submission1->id, + $submission3->id + ]; + + $userids = [ + $user1->id, + $user3->id + ]; + + $data = $DB->get_records('files', ['contextid' => $context1->id, 'component' => 'assignsubmission_file']); + $this->assertCount(6, $data); + + $data = $DB->get_records('assignsubmission_file', ['assignment' => $assign1->get_instance()->id]); + $this->assertCount(3, $data); + + // Records in the second assignment (not being touched). + $data = $DB->get_records('assignsubmission_file', ['assignment' => $assign2->get_instance()->id]); + $this->assertCount(2, $data); + + $deletedata = new \mod_assign\privacy\assign_plugin_request_data($context1, $assign1); + $deletedata->set_userids($userids); + $deletedata->populate_submissions_and_grades(); + \assignsubmission_file\privacy\provider::delete_submissions($deletedata); + $data = $DB->get_records('files', ['contextid' => $context1->id, 'component' => 'assignsubmission_file']); + $this->assertCount(2, $data); + + // Submission 1 and 3 have been removed. We should be left with submission2. + $data = $DB->get_records('assignsubmission_file', ['assignment' => $assign1->get_instance()->id]); + $this->assertCount(1, $data); + + // This should be untouched. + $data = $DB->get_records('assignsubmission_file', ['assignment' => $assign2->get_instance()->id]); + $this->assertCount(2, $data); + } } diff --git a/mod/assign/submission/onlinetext/classes/privacy/provider.php b/mod/assign/submission/onlinetext/classes/privacy/provider.php index fc5e2746312..fa660b9769a 100644 --- a/mod/assign/submission/onlinetext/classes/privacy/provider.php +++ b/mod/assign/submission/onlinetext/classes/privacy/provider.php @@ -29,7 +29,6 @@ defined('MOODLE_INTERNAL') || die(); 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\writer; use \core_privacy\local\request\contextlist; use \mod_assign\privacy\assign_plugin_request_data; @@ -41,7 +40,10 @@ use \mod_assign\privacy\assign_plugin_request_data; * @copyright 2018 Adrian Greeve * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements metadataprovider, \mod_assign\privacy\assignsubmission_provider { +class provider implements + \core_privacy\local\metadata\provider, + \mod_assign\privacy\assignsubmission_provider, + \mod_assign\privacy\assignsubmission_user_provider { /** * Return meta data about this plugin. @@ -79,6 +81,16 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission // No need. } + /** + * If you have tables that contain userids and you can generate entries in your tables without creating an + * entry in the assign_submission table then please fill in this method. + * + * @param \core_privacy\local\request\userlist $userlist The userlist object + */ + public static function get_userids_from_context(\core_privacy\local\request\userlist $userlist) { + // Not required. + } + /** * Export all user data for this plugin. * @@ -136,7 +148,7 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission ASSIGNSUBMISSION_ONLINETEXT_FILEAREA); // Delete the records in the table. - $DB->delete_records('assignsubmission_onlinetext', ['assignment' => $requestdata->get_assign()->get_instance()->id]); + $DB->delete_records('assignsubmission_onlinetext', ['assignment' => $requestdata->get_assignid()]); } /** @@ -157,7 +169,33 @@ class provider implements metadataprovider, \mod_assign\privacy\assignsubmission $submissionid); // Delete the records in the table. - $DB->delete_records('assignsubmission_onlinetext', ['assignment' => $deletedata->get_assign()->get_instance()->id, + $DB->delete_records('assignsubmission_onlinetext', ['assignment' => $deletedata->get_assignid(), 'submission' => $submissionid]); } + + /** + * Deletes all submissions for the submission ids / userids provided in a context. + * assign_plugin_request_data contains: + * - context + * - assign object + * - submission ids (pluginids) + * - user ids + * @param assign_plugin_request_data $deletedata A class that contains the relevant information required for deletion. + */ + public static function delete_submissions(assign_plugin_request_data $deletedata) { + global $DB; + + \core_plagiarism\privacy\provider::delete_plagiarism_for_users($deletedata->get_userids(), $deletedata->get_context()); + if (empty($deletedata->get_submissionids())) { + return; + } + + $fs = get_file_storage(); + list($sql, $params) = $DB->get_in_or_equal($deletedata->get_submissionids(), SQL_PARAMS_NAMED); + $fs->delete_area_files_select($deletedata->get_context()->id, + 'assignsubmission_onlinetext', ASSIGNSUBMISSION_ONLINETEXT_FILEAREA, $sql, $params); + + $params['assignid'] = $deletedata->get_assignid(); + $DB->delete_records_select('assignsubmission_onlinetext', "assignment = :assignid AND submission $sql", $params); + } } diff --git a/mod/assign/submission/onlinetext/tests/privacy_test.php b/mod/assign/submission/onlinetext/tests/privacy_test.php index ce1dd5062ad..ed7342c307a 100644 --- a/mod/assign/submission/onlinetext/tests/privacy_test.php +++ b/mod/assign/submission/onlinetext/tests/privacy_test.php @@ -67,7 +67,7 @@ class assignsubmission_online_privacy_testcase extends \mod_assign\tests\mod_ass * Quick test to make sure that get_metadata returns something. */ public function test_get_metadata() { - $collection = new \core_privacy\local\metadata\collection('assignsubmission_file'); + $collection = new \core_privacy\local\metadata\collection('assignsubmission_onlinetext'); $collection = \assignsubmission_onlinetext\privacy\provider::get_metadata($collection); $this->assertNotEmpty($collection); } @@ -161,4 +161,56 @@ class assignsubmission_online_privacy_testcase extends \mod_assign\tests\mod_ass // But there is for the second submission. $this->assertFalse($plugin2->is_empty($submission2)); } + + public function test_delete_submissions() { + global $DB; + + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + // Only makes submissions in the second assignment. + $user4 = $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'); + + $assign1 = $this->create_instance(['course' => $course]); + $assign2 = $this->create_instance(['course' => $course]); + + $context1 = $assign1->get_context(); + $context2 = $assign2->get_context(); + + $student1text = 'Student one\'s text.'; + list($plugin1, $submission1) = $this->create_online_submission($assign1, $user1, $student1text); + $student2text = 'Student two\'s text.'; + list($plugin2, $submission2) = $this->create_online_submission($assign1, $user2, $student2text); + $student3text = 'Student two\'s text.'; + list($plugin3, $submission3) = $this->create_online_submission($assign1, $user3, $student3text); + // Now for submissions in assignment two. + $student3text2 = 'Student two\'s text for the second assignment.'; + list($plugin4, $submission4) = $this->create_online_submission($assign2, $user3, $student3text2); + $student4text = 'Student four\'s text.'; + list($plugin5, $submission5) = $this->create_online_submission($assign2, $user4, $student4text); + + $data = $DB->get_records('assignsubmission_onlinetext', ['assignment' => $assign1->get_instance()->id]); + $this->assertCount(3, $data); + // Delete the submissions for user 1 and 3. + $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context1, $assign1); + $requestdata->set_userids([$user1->id, $user2->id]); + $requestdata->populate_submissions_and_grades(); + \assignsubmission_onlinetext\privacy\provider::delete_submissions($requestdata); + + // There should only be one record left for assignment one. + $data = $DB->get_records('assignsubmission_onlinetext', ['assignment' => $assign1->get_instance()->id]); + $this->assertCount(1, $data); + + // Check that the second assignment has not been touched. + $data = $DB->get_records('assignsubmission_onlinetext', ['assignment' => $assign2->get_instance()->id]); + $this->assertCount(2, $data); + } }