MDL-62289 tool_dataprivacy: Ensure all user data deleted.
authorAdrian Greeve <adrian@moodle.com>
Wed, 9 May 2018 02:16:39 +0000 (10:16 +0800)
committerAdrian Greeve <adrian@moodle.com>
Wed, 9 May 2018 02:16:39 +0000 (10:16 +0800)
We now do a comprehensive check and clean of user data when
a user context expires.

admin/tool/dataprivacy/classes/expired_user_contexts.php
admin/tool/dataprivacy/tests/expired_contexts_test.php

index 62d9485..e4b40e8 100644 (file)
@@ -115,12 +115,35 @@ class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
      * @return \context|false
      */
     protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
-        if (!$context = parent::delete_expired_context($privacymanager, $expiredctx)) {
+        $context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
+        if (!$context) {
+            api::delete_expired_context($expiredctx->get('contextid'));
             return false;
         }
 
-        // Delete the user.
+        if (!PHPUNIT_TEST) {
+            mtrace('Deleting context ' . $context->id . ' - ' .
+                shorten_text($context->get_context_name(true, true)));
+        }
+
+        // To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
+        // contexts for the user that may still exist and call delete_data_for_user().
         $user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
+        $approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
+        $contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
+
+        foreach ($contextlistcollection as $contextlist) {
+            $approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
+                $user,
+                $contextlist->get_component(),
+                $contextlist->get_contextids()
+            ));
+        }
+
+        $privacymanager->delete_data_for_user($approvedlistcollection);
+        api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
+
+        // Delete the user.
         delete_user($user);
 
         return $context;
index 7e62cc4..1e434a7 100644 (file)
@@ -84,6 +84,22 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
         $this->getDataGenerator()->enrol_user($user3->id, $course2->id, 'student');
         $this->getDataGenerator()->enrol_user($user4->id, $course3->id, 'student');
 
+        // Add an activity and some data for user 2.
+        $assignmod = $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
+        $data = (object) [
+            'assignment' => $assignmod->id,
+            'userid' => $user2->id,
+            'timecreated' => time(),
+            'timemodified' => time(),
+            'status' => 'new',
+            'groupid' => 0,
+            'attemptnumber' => 0,
+            'latest' => 1,
+        ];
+        $DB->insert_record('assign_submission', $data);
+        // We should have one record in the assign submission table.
+        $this->assertEquals(1, $DB->count_records('assign_submission'));
+
         // Users without lastaccess are skipped as well as users enroled in courses with no end date.
         $expired = new \tool_dataprivacy\expired_user_contexts();
         $numexpired = $expired->flag_expired();
@@ -109,6 +125,9 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
         $deleted = $expired->delete();
         $this->assertEquals(0, $deleted);
 
+        // No user data left in mod_assign.
+        $this->assertEquals(0, $DB->count_records('assign_submission'));
+
         // The user is deleted.
         $deleteduser = \core_user::get_user($user2->id, 'id, deleted', IGNORE_MISSING);
         $this->assertEquals(1, $deleteduser->deleted);