MDL-63510 mod_survey: Add support for removal of context users
authorMichael Hawkins <michaelh@moodle.com>
Wed, 3 Oct 2018 04:18:11 +0000 (12:18 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 22 Oct 2018 10:48:34 +0000 (12:48 +0200)
This issue is a part of the MDL-62560 Epic.

mod/survey/classes/privacy/provider.php
mod/survey/tests/privacy_test.php

index 4446564..b545e4b 100644 (file)
@@ -31,8 +31,10 @@ use context_helper;
 use context_module;
 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\helper;
 use core_privacy\local\request\transform;
+use core_privacy\local\request\userlist;
 use core_privacy\local\request\writer;
 
 require_once($CFG->dirroot . '/mod/survey/lib.php');
@@ -47,6 +49,7 @@ require_once($CFG->dirroot . '/mod/survey/lib.php');
  */
 class provider implements
     \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\core_userlist_provider,
     \core_privacy\local\request\plugin\provider {
 
     /**
@@ -113,6 +116,61 @@ 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;
+        }
+
+        $params = [
+            'survey' => 'survey',
+            'modulelevel' => CONTEXT_MODULE,
+            'contextid' => $context->id,
+        ];
+
+        $sql = "
+            SELECT sa.userid
+              FROM {survey} s
+              JOIN {modules} m
+                ON m.name = :survey
+              JOIN {course_modules} cm
+                ON cm.instance = s.id
+               AND cm.module = m.id
+              JOIN {context} ctx
+                ON ctx.instanceid = cm.id
+               AND ctx.contextlevel = :modulelevel
+              JOIN {survey_answers} sa
+                ON sa.survey = s.id
+             WHERE ctx.id = :contextid
+               AND s.template <> 0";
+
+        $userlist->add_from_sql('userid', $sql, $params);
+
+        $sql = "
+            SELECT sy.userid
+              FROM {survey} s
+              JOIN {modules} m
+                ON m.name = :survey
+              JOIN {course_modules} cm
+                ON cm.instance = s.id
+               AND cm.module = m.id
+              JOIN {context} ctx
+                ON ctx.instanceid = cm.id
+               AND ctx.contextlevel = :modulelevel
+              JOIN {survey_analysis} sy
+                ON sy.survey = s.id
+             WHERE ctx.id = :contextid
+               AND s.template <> 0";
+
+        $userlist->add_from_sql('userid', $sql, $params);
+    }
+
     /**
      * Export all user data for the specified user, in the specified contexts.
      *
@@ -275,6 +333,44 @@ class provider implements
         $DB->delete_records_select('survey_analysis', "survey $insql AND userid = :userid", $params);
     }
 
+    /**
+     * 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->contextlevel != CONTEXT_MODULE) {
+            return;
+        }
+
+        // Fetch the survey ID.
+        $sql = "
+            SELECT s.id
+              FROM {survey} s
+              JOIN {modules} m
+                ON m.name = :survey
+              JOIN {course_modules} cm
+                ON cm.instance = s.id
+               AND cm.module = m.id
+             WHERE cm.id = :cmid";
+        $params = [
+            'survey' => 'survey',
+            'cmid' => $context->instanceid,
+            ];
+        $surveyid = $DB->get_field_sql($sql, $params);
+        $userids = $userlist->get_userids();
+
+        // Delete all the things.
+        list($insql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+        $params['surveyid'] = $surveyid;
+
+        $DB->delete_records_select('survey_answers', "survey = :surveyid AND userid {$insql}", $params);
+        $DB->delete_records_select('survey_analysis', "survey = :surveyid AND userid {$insql}", $params);
+    }
+
     /**
      * Get a survey ID from its context.
      *
index c338073..75ea2f2 100644 (file)
@@ -29,6 +29,7 @@ global $CFG;
 
 use core_privacy\tests\provider_testcase;
 use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\approved_userlist;
 use core_privacy\local\request\transform;
 use core_privacy\local\request\writer;
 use mod_survey\privacy\provider;
@@ -85,6 +86,59 @@ class mod_survey_privacy_testcase extends provider_testcase {
         $this->assertTrue(in_array(context_module::instance($cm1c->cmid)->id, $contextids));
     }
 
+    /**
+     * Test for provider::test_get_users_in_context().
+     */
+    public function test_get_users_in_context() {
+        $dg = $this->getDataGenerator();
+        $component = 'mod_survey';
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+        $cm1a = $dg->create_module('survey', ['template' => 1, 'course' => $c1]);
+        $cm1b = $dg->create_module('survey', ['template' => 2, 'course' => $c1]);
+        $cm2 = $dg->create_module('survey', ['template' => 1, 'course' => $c2]);
+        $cm1acontext = context_module::instance($cm1a->cmid);
+        $cm1bcontext = context_module::instance($cm1b->cmid);
+        $cm2context = context_module::instance($cm2->cmid);
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $bothusers = [$u1->id, $u2->id];
+        sort($bothusers);
+
+        $this->create_answer($cm1a->id, 1, $u1->id);
+        $this->create_answer($cm1b->id, 1, $u1->id);
+        $this->create_answer($cm1b->id, 1, $u2->id);
+        $this->create_answer($cm2->id, 1, $u2->id);
+        $this->create_analysis($cm2->id, $u1->id);
+
+        // Cm1a should only contain u1.
+        $userlist = new \core_privacy\local\request\userlist($cm1acontext, $component);
+        provider::get_users_in_context($userlist);
+
+        $this->assertCount(1, $userlist);
+        $this->assertEquals([$u1->id], $userlist->get_userids());
+
+        // Cm1b should contain u1 and u2 (both have answers).
+        $userlist = new \core_privacy\local\request\userlist($cm1bcontext, $component);
+        provider::get_users_in_context($userlist);
+
+        $this->assertCount(2, $userlist);
+        $actual = $userlist->get_userids();
+        sort($actual);
+        $this->assertEquals($bothusers, $actual);
+
+        // Cm2 should contain u1 (analysis) and u2 (answer).
+        $userlist = new \core_privacy\local\request\userlist($cm2context, $component);
+        provider::get_users_in_context($userlist);
+
+        $this->assertCount(2, $userlist);
+        $actual = $userlist->get_userids();
+        sort($actual);
+        $this->assertEquals($bothusers, $actual);
+    }
+
     public function test_delete_data_for_all_users_in_context() {
         global $DB;
         $dg = $this->getDataGenerator();
@@ -190,6 +244,64 @@ class mod_survey_privacy_testcase extends provider_testcase {
         $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1c->id]));
     }
 
+    /**
+     * Test for provider::delete_data_for_users().
+     */
+    public function test_delete_data_for_users() {
+        global $DB;
+        $dg = $this->getDataGenerator();
+        $component = 'mod_survey';
+
+        $c1 = $dg->create_course();
+        $cm1a = $dg->create_module('survey', ['template' => 1, 'course' => $c1]);
+        $cm1b = $dg->create_module('survey', ['template' => 2, 'course' => $c1]);
+        $cm1c = $dg->create_module('survey', ['template' => 2, 'course' => $c1]);
+        $cm1acontext = context_module::instance($cm1a->cmid);
+        $cm1bcontext = context_module::instance($cm1b->cmid);
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+
+        $this->create_answer($cm1a->id, 1, $u1->id);
+        $this->create_answer($cm1a->id, 1, $u2->id);
+        $this->create_analysis($cm1a->id, $u1->id);
+        $this->create_analysis($cm1a->id, $u2->id);
+        $this->create_answer($cm1b->id, 1, $u2->id);
+        $this->create_analysis($cm1b->id, $u1->id);
+        $this->create_answer($cm1c->id, 1, $u1->id);
+        $this->create_analysis($cm1c->id, $u2->id);
+
+        // Confirm data exists before deletion.
+        $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1a->id]));
+        $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1c->id]));
+        $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u2->id, 'survey' => $cm1a->id]));
+        $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u2->id, 'survey' => $cm1b->id]));
+        $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u1->id, 'survey' => $cm1a->id]));
+        $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u1->id, 'survey' => $cm1b->id]));
+        $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1a->id]));
+        $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1c->id]));
+
+        // Ensure only approved user data is deleted.
+        $approveduserids = [$u1->id];
+        $approvedlist = new approved_userlist($cm1acontext, $component, $approveduserids);
+        provider::delete_data_for_users($approvedlist);
+
+        $this->assertFalse($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1a->id]));
+        $this->assertFalse($DB->record_exists('survey_analysis', ['userid' => $u1->id, 'survey' => $cm1a->id]));
+        $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u2->id, 'survey' => $cm1a->id]));
+        $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1a->id]));
+
+        $approveduserids = [$u1->id, $u2->id];
+        $approvedlist = new approved_userlist($cm1bcontext, $component, $approveduserids);
+        provider::delete_data_for_users($approvedlist);
+
+        $this->assertFalse($DB->record_exists('survey_answers', ['survey' => $cm1b->id]));
+        $this->assertFalse($DB->record_exists('survey_analysis', ['survey' => $cm1b->id]));
+
+        $this->assertTrue($DB->record_exists('survey_answers', ['userid' => $u1->id, 'survey' => $cm1c->id]));
+        $this->assertTrue($DB->record_exists('survey_analysis', ['userid' => $u2->id, 'survey' => $cm1c->id]));
+    }
+
     public function test_export_data_for_user() {
         global $DB;
         $dg = $this->getDataGenerator();