MDL-63501 mod_scorm: Add support for removal of context users
authorMichael Hawkins <michaelh@moodle.com>
Wed, 3 Oct 2018 02:15:37 +0000 (10:15 +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/scorm/classes/privacy/provider.php
mod/scorm/tests/privacy_test.php

index 3d6c1c7..bbb40b5 100644 (file)
@@ -28,9 +28,11 @@ defined('MOODLE_INTERNAL') || die();
 
 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;
 
 /**
@@ -41,6 +43,7 @@ use core_privacy\local\request\writer;
  */
 class provider implements
         \core_privacy\local\metadata\provider,
+        \core_privacy\local\request\core_userlist_provider,
         \core_privacy\local\request\plugin\provider {
 
     /**
@@ -103,6 +106,36 @@ 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;
+        }
+
+        $sql = "SELECT ss.userid
+                  FROM {%s} ss
+                  JOIN {modules} m
+                    ON m.name = 'scorm'
+                  JOIN {course_modules} cm
+                    ON cm.instance = ss.scormid
+                   AND cm.module = m.id
+                  JOIN {context} ctx
+                    ON ctx.instanceid = cm.id
+                   AND ctx.contextlevel = :modlevel
+                 WHERE ctx.id = :contextid";
+
+        $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];
+
+        $userlist->add_from_sql('userid', sprintf($sql, 'scorm_scoes_track'), $params);
+        $userlist->add_from_sql('userid', sprintf($sql, 'scorm_aicc_session'), $params);
+    }
+
     /**
      * Export all user data for the specified user, in the specified contexts.
      *
@@ -290,6 +323,40 @@ class provider implements
         static::delete_data('scorm_aicc_session', $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();
+
+        if (!is_a($context, \context_module::class)) {
+            return;
+        }
+
+        // Prepare SQL to gather all completed IDs.
+        $userids = $userlist->get_userids();
+        list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+
+        $sql = "SELECT ss.id
+                  FROM {%s} ss
+                  JOIN {modules} m
+                    ON m.name = 'scorm'
+                  JOIN {course_modules} cm
+                    ON cm.instance = ss.scormid
+                   AND cm.module = m.id
+                  JOIN {context} ctx
+                    ON ctx.instanceid = cm.id
+                 WHERE ctx.id = :contextid
+                   AND ss.userid $insql";
+        $params = array_merge($inparams, ['contextid' => $context->id]);
+
+        static::delete_data('scorm_scoes_track', $sql, $params);
+        static::delete_data('scorm_aicc_session', $sql, $params);
+    }
+
     /**
      * Delete data from $tablename with the IDs returned by $sql query.
      *
index 00811b7..e756671 100644 (file)
@@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
 
 use mod_scorm\privacy\provider;
 use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\approved_userlist;
 use core_privacy\local\request\writer;
 use core_privacy\tests\provider_testcase;
 
@@ -68,6 +69,28 @@ class mod_scorm_testcase extends provider_testcase {
         $this->assertContains($this->context->id, $contextlist->get_contextids());
     }
 
+    /**
+     * Test getting the user IDs for the context related to this plugin.
+     */
+    public function test_get_users_in_context() {
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $this->scorm_setup_test_scenario_data();
+        $component = 'mod_scorm';
+
+        $userlist = new \core_privacy\local\request\userlist($this->context, $component);
+        provider::get_users_in_context($userlist);
+
+        // Students 1 and 2 have attempts in the SCORM context, student 0 does not.
+        $this->assertCount(2, $userlist);
+
+        $expected = [$this->student1->id, $this->student2->id];
+        $actual = $userlist->get_userids();
+        sort($expected);
+        sort($actual);
+        $this->assertEquals($expected, $actual);
+    }
+
     /**
      * Test that data is exported correctly for this plugin.
      */
@@ -196,9 +219,59 @@ class mod_scorm_testcase extends provider_testcase {
         $this->assertEquals(2, $count);
     }
 
+    /**
+     * Test for provider::delete_data_for_users().
+     */
+    public function test_delete_data_for_users() {
+        global $DB;
+        $component = 'mod_scorm';
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $this->scorm_setup_test_scenario_data();
+
+        // Before deletion, we should have 8 entries in the scorm_scoes_track table.
+        $count = $DB->count_records('scorm_scoes_track');
+        $this->assertEquals(8, $count);
+        // Before deletion, we should have 4 entries in the scorm_aicc_session table.
+        $count = $DB->count_records('scorm_aicc_session');
+        $this->assertEquals(4, $count);
+
+        // Delete only student 1's data, retain student 2's data.
+        $approveduserids = [$this->student1->id];
+        $approvedlist = new approved_userlist($this->context, $component, $approveduserids);
+        provider::delete_data_for_users($approvedlist);
+
+        // After deletion, the scorm_scoes_track entries for the first student should have been deleted.
+        $count = $DB->count_records('scorm_scoes_track', ['userid' => $this->student1->id]);
+        $this->assertEquals(0, $count);
+        $count = $DB->count_records('scorm_scoes_track');
+        $this->assertEquals(4, $count);
+
+        // After deletion, the scorm_aicc_session entries for the first student should have been deleted.
+        $count = $DB->count_records('scorm_aicc_session', ['userid' => $this->student1->id]);
+        $this->assertEquals(0, $count);
+        $count = $DB->count_records('scorm_aicc_session');
+        $this->assertEquals(2, $count);
+
+        // Confirm that the SCORM hasn't been removed.
+        $scormcount = $DB->get_records('scorm');
+        $this->assertCount(1, (array) $scormcount);
+
+        // Delete scoes_track for student0 (nothing has to be removed).
+        $approveduserids = [$this->student0->id];
+        $approvedlist = new approved_userlist($this->context, $component, $approveduserids);
+        provider::delete_data_for_users($approvedlist);
+
+        $count = $DB->count_records('scorm_scoes_track');
+        $this->assertEquals(4, $count);
+        $count = $DB->count_records('scorm_aicc_session');
+        $this->assertEquals(2, $count);
+    }
+
     /**
      * Helper function to setup 3 users and 2 SCORM attempts for student1 and student2.
-     * $this->student0 is always created withot any attempt.
+     * $this->student0 is always created without any attempt.
      */
     protected function scorm_setup_test_scenario_data() {
         global $DB;