MDL-63498 mod_lti: Add support for removal of context users
authorMichael Hawkins <michaelh@moodle.com>
Tue, 2 Oct 2018 08:28:04 +0000 (16:28 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 22 Oct 2018 10:48:25 +0000 (12:48 +0200)
This issue is part of the MDL-62560 Epic.

mod/lti/classes/privacy/provider.php
mod/lti/tests/privacy_provider_test.php

index b5c7d41..1a42186 100644 (file)
@@ -25,9 +25,11 @@ namespace mod_lti\privacy;
 
 use core_privacy\local\metadata\collection;
 use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\approved_userlist;
 use core_privacy\local\request\contextlist;
 use core_privacy\local\request\helper;
 use core_privacy\local\request\transform;
+use core_privacy\local\request\userlist;
 use core_privacy\local\request\writer;
 
 defined('MOODLE_INTERNAL') || die();
@@ -40,6 +42,7 @@ defined('MOODLE_INTERNAL') || die();
  */
 class provider implements
     \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\core_userlist_provider,
     \core_privacy\local\request\plugin\provider {
 
     /**
@@ -157,6 +160,58 @@ class provider implements
         return $contextlist;
     }
 
+    /**
+     * Get the list of users who have data within a context.
+     *
+     * @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 (!is_a($context, \context_module::class)) {
+            return;
+        }
+
+        // Fetch all LTI submissions.
+        $sql = "SELECT ltisub.userid
+                  FROM {context} c
+            INNER JOIN {course_modules} cm
+                    ON cm.id = c.instanceid
+                   AND c.contextlevel = :contextlevel
+            INNER JOIN {modules} m
+                    ON m.id = cm.module
+                   AND m.name = :modname
+            INNER JOIN {lti} lti
+                    ON lti.id = cm.instance
+            INNER JOIN {lti_submission} ltisub
+                    ON ltisub.ltiid = lti.id
+                 WHERE c.id = :contextid";
+
+        $params = [
+            'modname' => 'lti',
+            'contextlevel' => CONTEXT_MODULE,
+            'contextid' => $context->id,
+        ];
+
+        $userlist->add_from_sql('userid', $sql, $params);
+
+        // Fetch all LTI types.
+        $sql = "SELECT ltit.createdby AS userid
+                 FROM {context} c
+                 JOIN {course} course
+                   ON c.contextlevel = :contextlevel
+                  AND c.instanceid = course.id
+                 JOIN {lti_types} ltit
+                   ON ltit.course = course.id
+                WHERE c.id = :contextid";
+
+        $params = [
+            'contextlevel' => CONTEXT_COURSE,
+            'contextid' => $context->id,
+        ];
+        $userlist->add_from_sql('userid', $sql, $params);
+    }
+
     /**
      * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
      *
@@ -209,6 +264,27 @@ class provider implements
         }
     }
 
+    /**
+     * Delete multiple users within a single context.
+     *
+     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
+     */
+    public static function delete_data_for_users(approved_userlist $userlist) {
+        global $DB;
+
+        $context = $userlist->get_context();
+
+        if ($context instanceof \context_module) {
+            $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
+
+            list($insql, $inparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
+            $sql = "ltiid = :instanceid AND userid {$insql}";
+            $params = array_merge(['instanceid' => $instanceid], $inparams);
+
+            $DB->delete_records_select('lti_submission', $sql, $params);
+        }
+    }
+
     /**
      * Export personal data for the given approved_contextlist related to LTI submissions.
      *
index 1506160..ede951f 100644 (file)
@@ -112,6 +112,47 @@ class mod_lti_privacy_provider_testcase extends \core_privacy\tests\provider_tes
         $this->assertEquals(SYSCONTEXTID, $contextforsystem->id);
     }
 
+    /**
+     * Test for provider::test_get_users_in_context()
+     */
+    public function test_get_users_in_context() {
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+        $component = 'mod_lti';
+
+        // The LTI activity the user will have submitted something for.
+        $lti1 = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
+
+        // Another LTI activity that has no user activity.
+        $lti2 = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
+
+        // Create user which will make a submission each.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $this->create_lti_submission($lti1->id, $user1->id);
+        $this->create_lti_submission($lti1->id, $user2->id);
+
+        $context = context_module::instance($lti1->cmid);
+        $userlist = new \core_privacy\local\request\userlist($context, $component);
+        provider::get_users_in_context($userlist);
+
+        $this->assertCount(2, $userlist);
+        $expected = [$user1->id, $user2->id];
+        $actual = $userlist->get_userids();
+        sort($expected);
+        sort($actual);
+
+        $this->assertEquals($expected, $actual);
+
+        $context = context_module::instance($lti2->cmid);
+        $userlist = new \core_privacy\local\request\userlist($context, $component);
+        provider::get_users_in_context($userlist);
+
+        $this->assertEmpty($userlist);
+    }
+
     /**
      * Test for provider::export_user_data().
      */
@@ -288,6 +329,51 @@ class mod_lti_privacy_provider_testcase extends \core_privacy\tests\provider_tes
         $this->assertEquals($user2->id, $lastsubmission->userid);
     }
 
+    /**
+     * Test for provider::delete_data_for_users().
+     */
+    public function test_delete_data_for_users() {
+        global $DB;
+        $component = 'mod_lti';
+
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+
+        $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
+
+        // Create users that will make submissions.
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+
+        $this->create_lti_submission($lti->id, $user1->id);
+        $this->create_lti_submission($lti->id, $user2->id);
+        $this->create_lti_submission($lti->id, $user3->id);
+
+        // Before deletion we should have 2 responses.
+        $count = $DB->count_records('lti_submission', ['ltiid' => $lti->id]);
+        $this->assertEquals(3, $count);
+
+        $context = \context_module::instance($lti->cmid);
+        $approveduserids = [$user1->id, $user2->id];
+        $approvedlist = new core_privacy\local\request\approved_userlist($context, $component, $approveduserids);
+        provider::delete_data_for_users($approvedlist);
+
+        // After deletion the lti submission for the first two users should have been deleted.
+        list($insql, $inparams) = $DB->get_in_or_equal($approveduserids, SQL_PARAMS_NAMED);
+        $sql = "ltiid = :ltiid AND userid {$insql}";
+        $params = array_merge($inparams, ['ltiid' => $lti->id]);
+        $count = $DB->count_records_select('lti_submission', $sql, $params);
+        $this->assertEquals(0, $count);
+
+        // Check the submission for the third user is still there.
+        $ltisubmission = $DB->get_records('lti_submission');
+        $this->assertCount(1, $ltisubmission);
+        $lastsubmission = reset($ltisubmission);
+        $this->assertEquals($user3->id, $lastsubmission->userid);
+    }
+
     /**
      * Mimicks the creation of an LTI submission.
      *