MDL-63497 mod_lesson: Add support for removal of context users
authorMichael Hawkins <michaelh@moodle.com>
Mon, 1 Oct 2018 09:13:31 +0000 (17:13 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 22 Oct 2018 10:48:33 +0000 (12:48 +0200)
This issue is a part of the MDL-62560 Epic.

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

index 8e55bac..f525be2 100644 (file)
@@ -32,8 +32,10 @@ use context_module;
 use stdClass;
 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/lesson/locallib.php');
@@ -51,6 +53,7 @@ require_once($CFG->dirroot . '/mod/lesson/pagetypes/multichoice.php');
  */
 class provider implements
     \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\core_userlist_provider,
     \core_privacy\local\request\plugin\provider,
     \core_privacy\local\request\user_preference_provider {
 
@@ -166,6 +169,54 @@ 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 = [
+            'lesson' => 'lesson',
+            'modulelevel' => CONTEXT_MODULE,
+            'contextid' => $context->id,
+        ];
+
+        // Mapping of lesson tables which may contain user data.
+        $joins = [
+            'lesson_attempts',
+            'lesson_branch',
+            'lesson_grades',
+            'lesson_overrides',
+            'lesson_timer',
+        ];
+
+        foreach ($joins as $join) {
+            $sql = "
+                SELECT lx.userid
+                  FROM {lesson} l
+                  JOIN {modules} m
+                    ON m.name = :lesson
+                  JOIN {course_modules} cm
+                    ON cm.instance = l.id
+                   AND cm.module = m.id
+                  JOIN {context} ctx
+                    ON ctx.instanceid = cm.id
+                   AND ctx.contextlevel = :modulelevel
+                  JOIN {{$join}} lx
+                    ON lx.lessonid = l.id
+                 WHERE ctx.id = :contextid";
+
+            $userlist->add_from_sql('userid', $sql, $params);
+        }
+    }
+
     /**
      * Export all user data for the specified user, in the specified contexts.
      *
@@ -445,6 +496,43 @@ class provider implements
         $DB->delete_records_select('lesson_overrides', $sql, $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();
+        $lessonid = static::get_lesson_id_from_context($context);
+        $userids = $userlist->get_userids();
+
+        if (empty($lessonid)) {
+            return;
+        }
+
+        // Prepare the SQL we'll need below.
+        list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+        $sql = "lessonid = :lessonid AND userid {$insql}";
+        $params = array_merge($inparams, ['lessonid' => $lessonid]);
+
+        // Delete the attempt files.
+        $fs = get_file_storage();
+        $recordset = $DB->get_recordset_select('lesson_attempts', $sql, $params, '', 'id, lessonid');
+        foreach ($recordset as $record) {
+            $fs->delete_area_files($context->id, 'mod_lesson', 'essay_responses', $record->id);
+        }
+        $recordset->close();
+
+        // Delete all the things.
+        $DB->delete_records_select('lesson_attempts', $sql, $params);
+        $DB->delete_records_select('lesson_branch', $sql, $params);
+        $DB->delete_records_select('lesson_grades', $sql, $params);
+        $DB->delete_records_select('lesson_timer', $sql, $params);
+        $DB->delete_records_select('lesson_overrides', $sql, $params);
+    }
+
     /**
      * Get a survey ID from its context.
      *
index 896aa67..0cd6c23 100644 (file)
@@ -108,6 +108,60 @@ class mod_lesson_privacy_testcase extends provider_testcase {
         $this->assertTrue(in_array($cm3ctx->id, $contextids));
     }
 
+    /*
+     * Test for provider::get_users_in_context().
+     */
+    public function test_get_users_in_context() {
+        $dg = $this->getDataGenerator();
+        $c1 = $dg->create_course();
+        $component = 'mod_lesson';
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $u5 = $dg->create_user();
+        $u6 = $dg->create_user();
+
+        $cm1 = $dg->create_module('lesson', ['course' => $c1]);
+        $cm2 = $dg->create_module('lesson', ['course' => $c1]);
+
+        $cm1ctx = context_module::instance($cm1->cmid);
+        $cm2ctx = context_module::instance($cm2->cmid);
+
+        $this->create_attempt($cm1, $u1);
+        $this->create_grade($cm1, $u2);
+        $this->create_timer($cm1, $u3);
+        $this->create_branch($cm1, $u4);
+        $this->create_override($cm1, $u5);
+
+        $this->create_attempt($cm2, $u6);
+        $this->create_grade($cm2, $u6);
+        $this->create_timer($cm2, $u6);
+        $this->create_branch($cm2, $u6);
+        $this->create_override($cm2, $u6);
+
+        $context = context_module::instance($cm1->cmid);
+        $userlist = new \core_privacy\local\request\userlist($context, $component);
+        provider::get_users_in_context($userlist);
+        $userids = $userlist->get_userids();
+
+        $this->assertCount(5, $userids);
+        $expected = [$u1->id, $u2->id, $u3->id, $u4->id, $u5->id];
+        $actual = $userids;
+        sort($expected);
+        sort($actual);
+        $this->assertEquals($expected, $actual);
+
+        $context = context_module::instance($cm2->cmid);
+        $userlist = new \core_privacy\local\request\userlist($context, $component);
+        provider::get_users_in_context($userlist);
+        $userids = $userlist->get_userids();
+
+        $this->assertCount(1, $userids);
+        $this->assertEquals([$u6->id], $userids);
+    }
+
     public function test_delete_data_for_all_users_in_context() {
         global $DB;
         $dg = $this->getDataGenerator();
@@ -293,6 +347,85 @@ class mod_lesson_privacy_testcase extends provider_testcase {
         $this->assertTrue($DB->record_exists('lesson_overrides', ['userid' => $u1->id, 'lessonid' => $cm2->id]));
     }
 
+    /*
+     * Test for provider::delete_data_for_users().
+     */
+    public function test_delete_data_for_users() {
+        global $DB;
+        $dg = $this->getDataGenerator();
+        $c1 = $dg->create_course();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+
+        $cm1 = $dg->create_module('lesson', ['course' => $c1]);
+        $cm2 = $dg->create_module('lesson', ['course' => $c1]);
+        $cm3 = $dg->create_module('lesson', ['course' => $c1]);
+        $context1 = context_module::instance($cm1->cmid);
+        $context3 = context_module::instance($cm3->cmid);
+
+        $this->create_attempt($cm1, $u1);
+        $this->create_grade($cm1, $u1);
+        $this->create_timer($cm1, $u1);
+        $this->create_branch($cm1, $u1);
+        $this->create_override($cm1, $u1);
+        $this->create_attempt($cm1, $u2);
+        $this->create_grade($cm1, $u2);
+        $this->create_timer($cm1, $u2);
+        $this->create_branch($cm1, $u2);
+        $this->create_override($cm1, $u2);
+
+        $this->create_attempt($cm2, $u1);
+        $this->create_grade($cm2, $u1);
+        $this->create_timer($cm2, $u1);
+        $this->create_branch($cm2, $u1);
+        $this->create_override($cm2, $u1);
+        $this->create_attempt($cm2, $u2);
+        $this->create_grade($cm2, $u2);
+        $this->create_timer($cm2, $u2);
+        $this->create_branch($cm2, $u2);
+        $this->create_override($cm2, $u2);
+
+        $assertnochange = function($user, $cm) use ($DB) {
+            $this->assertTrue($DB->record_exists('lesson_attempts', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertTrue($DB->record_exists('lesson_grades', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertTrue($DB->record_exists('lesson_timer', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertTrue($DB->record_exists('lesson_branch', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertTrue($DB->record_exists('lesson_overrides', ['userid' => $user->id, 'lessonid' => $cm->id]));
+        };
+
+        $assertdeleted = function($user, $cm) use ($DB) {
+            $this->assertFalse($DB->record_exists('lesson_attempts', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertFalse($DB->record_exists('lesson_grades', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertFalse($DB->record_exists('lesson_timer', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertFalse($DB->record_exists('lesson_branch', ['userid' => $user->id, 'lessonid' => $cm->id]));
+            $this->assertFalse($DB->record_exists('lesson_overrides', ['userid' => $user->id, 'lessonid' => $cm->id]));
+        };
+
+        // Confirm existing state.
+        $assertnochange($u1, $cm1);
+        $assertnochange($u1, $cm2);
+        $assertnochange($u2, $cm1);
+        $assertnochange($u2, $cm2);
+
+        // Delete another module: no change.
+        $approveduserlist = new core_privacy\local\request\approved_userlist($context3, 'mod_lesson', [$u1->id]);
+        provider::delete_data_for_users($approveduserlist);
+
+        $assertnochange($u1, $cm1);
+        $assertnochange($u1, $cm2);
+        $assertnochange($u2, $cm1);
+        $assertnochange($u2, $cm2);
+
+        // Delete cm1 for u1: no change for u2 and in cm2.
+        $approveduserlist = new core_privacy\local\request\approved_userlist($context1, 'mod_lesson', [$u1->id]);
+        provider::delete_data_for_users($approveduserlist);
+
+        $assertdeleted($u1, $cm1);
+        $assertnochange($u1, $cm2);
+        $assertnochange($u2, $cm1);
+        $assertnochange($u2, $cm2);
+    }
+
     public function test_export_data_for_user_overrides() {
         $dg = $this->getDataGenerator();
         $c1 = $dg->create_course();