Merge branch 'MDL-61959-master' of git://github.com/sarjona/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Mon, 30 Apr 2018 02:16:23 +0000 (10:16 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 30 Apr 2018 02:16:23 +0000 (10:16 +0800)
14 files changed:
mod/scorm/classes/privacy/provider.php [new file with mode: 0644]
mod/scorm/lang/en/scorm.php
mod/scorm/report/basic/classes/privacy/provider.php [new file with mode: 0644]
mod/scorm/report/basic/lang/en/scormreport_basic.php
mod/scorm/report/basic/tests/privacy_test.php [new file with mode: 0644]
mod/scorm/report/graphs/classes/privacy/provider.php [new file with mode: 0644]
mod/scorm/report/graphs/lang/en/scormreport_graphs.php
mod/scorm/report/interactions/classes/privacy/provider.php [new file with mode: 0644]
mod/scorm/report/interactions/lang/en/scormreport_interactions.php
mod/scorm/report/interactions/tests/privacy_test.php [new file with mode: 0644]
mod/scorm/report/objectives/classes/privacy/provider.php [new file with mode: 0644]
mod/scorm/report/objectives/lang/en/scormreport_objectives.php
mod/scorm/report/objectives/tests/privacy_test.php [new file with mode: 0644]
mod/scorm/tests/privacy_test.php [new file with mode: 0644]

diff --git a/mod/scorm/classes/privacy/provider.php b/mod/scorm/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..855499e
--- /dev/null
@@ -0,0 +1,295 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    mod_scorm
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_scorm\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\contextlist;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\transform;
+use core_privacy\local\request\writer;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+        \core_privacy\local\metadata\provider,
+        \core_privacy\local\request\plugin\provider {
+
+    /**
+     * Return the fields which contain personal data.
+     *
+     * @param   collection $collection The initialised collection to add items to.
+     * @return  collection A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $collection->add_database_table('scorm_scoes_track', [
+                'userid' => 'privacy:metadata:userid',
+                'attempt' => 'privacy:metadata:attempt',
+                'element' => 'privacy:metadata:scoes_track:element',
+                'value' => 'privacy:metadata:scoes_track:value',
+                'timemodified' => 'privacy:metadata:timemodified'
+            ], 'privacy:metadata:scorm_scoes_track');
+
+        $collection->add_database_table('scorm_aicc_session', [
+                'userid' => 'privacy:metadata:userid',
+                'scormmode' => 'privacy:metadata:aicc_session:scormmode',
+                'scormstatus' => 'privacy:metadata:aicc_session:scormstatus',
+                'attempt' => 'privacy:metadata:attempt',
+                'lessonstatus' => 'privacy:metadata:aicc_session:lessonstatus',
+                'sessiontime' => 'privacy:metadata:aicc_session:sessiontime',
+                'timecreated' => 'privacy:metadata:aicc_session:timecreated',
+                'timemodified' => 'privacy:metadata:timemodified',
+            ], 'privacy:metadata:scorm_aicc_session');
+
+        $collection->add_external_location_link('aicc', [
+                'data' => 'privacy:metadata:aicc:data'
+            ], 'privacy:metadata:aicc:externalpurpose');
+
+        return $collection;
+    }
+
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param int $userid The user to search.
+     * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
+     */
+    public static function get_contexts_for_userid(int $userid) : contextlist {
+        $sql = "SELECT ctx.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
+                   AND ctx.contextlevel = :modlevel
+                 WHERE ss.userid = :userid";
+
+        $params = ['modlevel' => CONTEXT_MODULE, 'userid' => $userid];
+        $contextlist = new contextlist();
+        $contextlist->add_from_sql(sprintf($sql, 'scorm_scoes_track'), $params);
+        $contextlist->add_from_sql(sprintf($sql, 'scorm_aicc_session'), $params);
+
+        return $contextlist;
+    }
+
+    /**
+     * Export all user data for the specified user, in the specified contexts.
+     *
+     * @param approved_contextlist $contextlist The approved contexts to export information for.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        global $DB;
+
+        // Remove contexts different from COURSE_MODULE.
+        $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
+            if ($context->contextlevel == CONTEXT_MODULE) {
+                $carry[] = $context->id;
+            }
+            return $carry;
+        }, []);
+
+        if (empty($contexts)) {
+            return;
+        }
+
+        $userid = $contextlist->get_user()->id;
+        list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+
+        // Get scoes_track data.
+        $sql = "SELECT ss.id,
+                       ss.attempt,
+                       ss.element,
+                       ss.value,
+                       ss.timemodified,
+                       ctx.id as contextid
+                  FROM {scorm_scoes_track} ss
+                  JOIN {course_modules} cm
+                    ON cm.instance = ss.scormid
+                  JOIN {context} ctx
+                    ON ctx.instanceid = cm.id
+                 WHERE ctx.id $insql
+                   AND ss.userid = :userid";
+        $params = array_merge($inparams, ['userid' => $userid]);
+
+        $alldata = [];
+        $scoestracks = $DB->get_recordset_sql($sql, $params);
+        foreach ($scoestracks as $track) {
+            $alldata[$track->contextid][$track->attempt][] = (object)[
+                    'element' => $track->element,
+                    'value' => $track->value,
+                    'timemodified' => transform::datetime($track->timemodified),
+                ];
+        }
+        $scoestracks->close();
+
+        // The scoes_track data is organised in: {Course name}/{SCORM activity name}/attempt-X.json.
+        // where X is the attempt number.
+        array_walk($alldata, function($attemptsdata, $contextid) {
+            $context = \context::instance_by_id($contextid);
+            array_walk($attemptsdata, function($data, $attempt) use ($context) {
+                writer::with_context($context)->export_related_data(
+                    [],
+                    'attempt-'.$attempt,
+                    (object)['scoestrack' => $data]
+                );
+            });
+        });
+
+        // Get aicc_session data.
+        $sql = "SELECT ss.id,
+                       ss.scormmode,
+                       ss.scormstatus,
+                       ss.attempt,
+                       ss.lessonstatus,
+                       ss.sessiontime,
+                       ss.timecreated,
+                       ss.timemodified,
+                       ctx.id as contextid
+                  FROM {scorm_aicc_session} ss
+                  JOIN {course_modules} cm
+                    ON cm.instance = ss.scormid
+                  JOIN {context} ctx
+                    ON ctx.instanceid = cm.id
+                 WHERE ctx.id $insql
+                   AND ss.userid = :userid";
+        $params = array_merge($inparams, ['userid' => $userid]);
+
+        $alldata = [];
+        $aiccsessions = $DB->get_recordset_sql($sql, $params);
+        foreach ($aiccsessions as $aiccsession) {
+            $alldata[$aiccsession->contextid][] = (object)[
+                    'scormmode' => $aiccsession->scormmode,
+                    'scormstatus' => $aiccsession->scormstatus,
+                    'lessonstatus' => $aiccsession->lessonstatus,
+                    'attempt' => $aiccsession->attempt,
+                    'sessiontime' => $aiccsession->sessiontime,
+                    'timecreated' => transform::datetime($aiccsession->timecreated),
+                    'timemodified' => transform::datetime($aiccsession->timemodified),
+                ];
+        }
+        $aiccsessions->close();
+
+        // The aicc_session data is organised in: {Course name}/{SCORM activity name}/aiccsession.json.
+        // In this case, the attempt hasn't been included in the json file because it can be null.
+        array_walk($alldata, function($data, $contextid) {
+            $context = \context::instance_by_id($contextid);
+            writer::with_context($context)->export_related_data(
+                [],
+                'aiccsession',
+                (object)['sessions' => $data]
+            );
+        });
+    }
+
+    /**
+     * Delete all user data which matches the specified context.
+     *
+     * @param context $context A user context.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        // This should not happen, but just in case.
+        if ($context->contextlevel != CONTEXT_MODULE) {
+            return;
+        }
+
+        // Prepare SQL to gather all IDs to delete.
+        $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
+                 WHERE cm.id = :cmid";
+        $params = ['cmid' => $context->instanceid];
+
+        static::delete_data('scorm_scoes_track', $sql, $params);
+        static::delete_data('scorm_aicc_session', $sql, $params);
+    }
+
+    /**
+     * Delete all user data for the specified user, in the specified contexts.
+     *
+     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        global $DB;
+
+        // Remove contexts different from COURSE_MODULE.
+        $contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
+            if ($context->contextlevel == CONTEXT_MODULE) {
+                $carry[] = $context->id;
+            }
+            return $carry;
+        }, []);
+
+        if (empty($contextids)) {
+            return;
+        }
+        $userid = $contextlist->get_user()->id;
+        // Prepare SQL to gather all completed IDs.
+        list($insql, $inparams) = $DB->get_in_or_equal($contextids, 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 ss.userid = :userid
+                   AND ctx.id $insql";
+        $params = array_merge($inparams, ['userid' => $userid]);
+
+        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.
+     *
+     * @param  string $tablename  Table name where executing the SQL query.
+     * @param  string $sql    SQL query for getting the IDs of the scoestrack entries to delete.
+     * @param  array  $params SQL params for the query.
+     */
+    protected static function delete_data(string $tablename, string $sql, array $params) {
+        global $DB;
+
+        $scoestracksids = $DB->get_fieldset_sql(sprintf($sql, $tablename), $params);
+        if (!empty($scoestracksids)) {
+            list($insql, $inparams) = $DB->get_in_or_equal($scoestracksids, SQL_PARAMS_NAMED);
+            $DB->delete_records_select($tablename, "id $insql", $inparams);
+        }
+    }
+}
index fc7ada1..c541a07 100644 (file)
@@ -332,6 +332,20 @@ $string['position_error'] = 'The {$a->tag} tag can\'t be child of {$a->parent} t
 $string['preferencesuser'] = 'Preferences for this report';
 $string['preferencespage'] = 'Preferences just for this page';
 $string['prev'] = 'Previous';
+$string['privacy:metadata:aicc:data'] = 'Personal data passed through from the AICC/SCORM subsystem.';
+$string['privacy:metadata:aicc:externalpurpose'] = 'This plugin sends data externally using the AICC HACP.';
+$string['privacy:metadata:aicc_session:lessonstatus'] = 'The lesson status to be tracked';
+$string['privacy:metadata:aicc_session:scormmode'] = 'The mode of the element to be tracked';
+$string['privacy:metadata:aicc_session:scormstatus'] = 'The status of the element to be tracked';
+$string['privacy:metadata:aicc_session:sessiontime'] = 'The session time to be tracked';
+$string['privacy:metadata:aicc_session:timecreated'] = 'The time when the tracked element was created';
+$string['privacy:metadata:attempt'] = 'The attempt number';
+$string['privacy:metadata:scoes_track:element'] = 'The name of the element to be tracked';
+$string['privacy:metadata:scoes_track:value'] = 'The value of the given element';
+$string['privacy:metadata:scorm_aicc_session'] = 'The session information of the AICC HACP';
+$string['privacy:metadata:scorm_scoes_track'] = 'The tracked data of the SCOes belonging to the activity';
+$string['privacy:metadata:timemodified'] = 'The time when the tracked element was last modified';
+$string['privacy:metadata:userid'] = 'The ID of the user who accessed the SCORM activity';
 $string['protectpackagedownloads'] = 'Protect package downloads';
 $string['protectpackagedownloads_desc'] = 'If enabled, SCORM packages can be downloaded only if the user has the course:manageactivities capability. If disabled, SCORM packages can always be downloaded (by mobile or other means).';
 $string['raw'] = 'Raw score';
diff --git a/mod/scorm/report/basic/classes/privacy/provider.php b/mod/scorm/report/basic/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..1f0c72c
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for scormreport_basic.
+ *
+ * @package    scormreport_basic
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace scormreport_basic\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+
+/**
+ * Privacy Subsystem for scormreport_basic.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+    \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\user_preference_provider {
+
+    /**
+     * Returns meta data about this system.
+     *
+     * @param  collection $collection The initialised item collection to add items to.
+     * @return collection A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        // User preferences shared between different scorm reports.
+        $collection->add_user_preference('scorm_report_pagesize', 'privacy:metadata:preference:scorm_report_pagesize');
+
+        // User preferences specific for this scorm report.
+        $collection->add_user_preference('scorm_report_detailed', 'privacy:metadata:preference:scorm_report_detailed');
+
+        return $collection;
+    }
+
+    /**
+     * Store all user preferences for the plugin.
+     *
+     * @param  int $userid The userid of the user whose data is to be exported.
+     */
+    public static function export_user_preferences(int $userid) {
+        static::get_and_export_user_preference($userid, 'scorm_report_pagesize');
+        static::get_and_export_user_preference($userid, 'scorm_report_detailed', true);
+    }
+
+    /**
+     * Get and export a user preference.
+     *
+     * @param  int     $userid The userid of the user whose data is to be exported.
+     * @param  string  $userpreference The user preference to export.
+     * @param  boolean $transform If true, transform value to yesno.
+     */
+    protected static function get_and_export_user_preference(int $userid, string $userpreference, $transform = false) {
+        $prefvalue = get_user_preferences($userpreference, null, $userid);
+        if ($prefvalue !== null) {
+            if ($transform) {
+                $transformedvalue = transform::yesno($prefvalue);
+            } else {
+                $transformedvalue = $prefvalue;
+            }
+            writer::export_user_preference(
+                'scormreport_basic',
+                $userpreference,
+                $transformedvalue,
+                get_string('privacy:metadata:preference:'.$userpreference, 'scormreport_basic')
+            );
+        }
+    }
+}
index 7a03152..55171eb 100644 (file)
@@ -24,3 +24,5 @@
  */
 
 $string['pluginname'] = 'Basic report';
+$string['privacy:metadata:preference:scorm_report_detailed'] = 'Whether to track details in the SCORM basic report';
+$string['privacy:metadata:preference:scorm_report_pagesize'] = 'Number of users to display in the SCORM reports';
diff --git a/mod/scorm/report/basic/tests/privacy_test.php b/mod/scorm/report/basic/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..ff06ce5
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the scormreport_basic implementation of the privacy API.
+ *
+ * @package    scormreport_basic
+ * @category   test
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\request\writer;
+use scormreport_basic\privacy\provider;
+
+/**
+ * Unit tests for the scormreport_basic implementation of the privacy API.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class scormreport_basic_privacy_testcase extends \core_privacy\tests\provider_testcase {
+
+    /**
+     * Basic setup for these tests.
+     */
+    public function setUp() {
+        $this->resetAfterTest(true);
+    }
+
+    /**
+     * Ensure that export_user_preferences returns no data if the user has no data.
+     */
+    public function test_export_user_preferences_not_defined() {
+        $user = \core_user::get_user_by_username('admin');
+        provider::export_user_preferences($user->id);
+
+        $writer = writer::with_context(\context_system::instance());
+        $this->assertFalse($writer->has_any_data());
+    }
+
+    /**
+     * Ensure that export_user_preferences returns single preferences.
+     */
+    public function test_export_user_preferences_single() {
+        // Define a user preference.
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        set_user_preference('scorm_report_detailed', 1);
+        set_user_preference('scorm_report_pagesize', 50);
+
+        // Validate exported data.
+        provider::export_user_preferences($user->id);
+        $context = \context_user::instance($user->id);
+        $writer = writer::with_context($context);
+        $this->assertTrue($writer->has_any_data());
+        $prefs = $writer->get_user_preferences('scormreport_basic');
+        $this->assertCount(2, (array) $prefs);
+        $this->assertEquals(
+            get_string('privacy:metadata:preference:scorm_report_detailed', 'scormreport_basic'),
+            $prefs->scorm_report_detailed->description
+        );
+        $this->assertEquals(get_string('yes'), $prefs->scorm_report_detailed->value);
+        $this->assertEquals(
+            get_string('privacy:metadata:preference:scorm_report_pagesize', 'scormreport_basic'),
+            $prefs->scorm_report_pagesize->description
+        );
+        $this->assertEquals(50, $prefs->scorm_report_pagesize->value);
+    }
+}
diff --git a/mod/scorm/report/graphs/classes/privacy/provider.php b/mod/scorm/report/graphs/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..d68c521
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for scormreport_graphs.
+ *
+ * @package    scormreport_graphs
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace scormreport_graphs\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for scormreport_graphs implementing null_provider.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 81bdbd7..5e463d3 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['invaliddata'] = 'Not enough data';
 $string['participants'] = 'Number of participants';
-$string['pluginname'] = 'Graph report';
 $string['percent'] = 'Percent(%) secured';
-$string['invaliddata'] = 'Not enough data';
+$string['pluginname'] = 'Graph report';
+$string['privacy:metadata'] = 'The Graph report only shows data stored in other locations.';
 
 
 
diff --git a/mod/scorm/report/interactions/classes/privacy/provider.php b/mod/scorm/report/interactions/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..06c10db
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for scormreport_interactions.
+ *
+ * @package    scormreport_interactions
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace scormreport_interactions\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+
+/**
+ * Privacy Subsystem for scormreport_interactions.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+    \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\user_preference_provider {
+
+    /**
+     * Returns meta data about this system.
+     *
+     * @param  collection $collection The initialised item collection to add items to.
+     * @return collection A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        // User preferences shared between different scorm reports.
+        $collection->add_user_preference('scorm_report_pagesize', 'privacy:metadata:preference:scorm_report_pagesize');
+
+        // User preferences specific for this scorm report.
+        $collection->add_user_preference(
+            'scorm_report_interactions_qtext',
+            'privacy:metadata:preference:scorm_report_interactions_qtext'
+        );
+        $collection->add_user_preference(
+            'scorm_report_interactions_resp',
+            'privacy:metadata:preference:scorm_report_interactions_resp'
+        );
+        $collection->add_user_preference(
+            'scorm_report_interactions_right',
+            'privacy:metadata:preference:scorm_report_interactions_right'
+        );
+        $collection->add_user_preference(
+            'scorm_report_interactions_result',
+            'privacy:metadata:preference:scorm_report_interactions_result'
+        );
+
+        return $collection;
+    }
+
+    /**
+     * Store all user preferences for the plugin.
+     *
+     * @param  int $userid The userid of the user whose data is to be exported.
+     */
+    public static function export_user_preferences(int $userid) {
+        static::get_and_export_user_preference($userid, 'scorm_report_pagesize');
+        static::get_and_export_user_preference($userid, 'scorm_report_interactions_qtext', true);
+        static::get_and_export_user_preference($userid, 'scorm_report_interactions_resp', true);
+        static::get_and_export_user_preference($userid, 'scorm_report_interactions_right', true);
+        static::get_and_export_user_preference($userid, 'scorm_report_interactions_result', true);
+    }
+
+    /**
+     * Get and export a user preference.
+     *
+     * @param  int     $userid The userid of the user whose data is to be exported.
+     * @param  string  $userpreference The user preference to export.
+     * @param  boolean $transform If true, transform value to yesno.
+     */
+    protected static function get_and_export_user_preference(int $userid, string $userpreference, $transform = false) {
+        $prefvalue = get_user_preferences($userpreference, null, $userid);
+        if ($prefvalue !== null) {
+            if ($transform) {
+                $transformedvalue = transform::yesno($prefvalue);
+            } else {
+                $transformedvalue = $prefvalue;
+            }
+            writer::export_user_preference(
+                'scormreport_interactions',
+                $userpreference,
+                $transformedvalue,
+                get_string('privacy:metadata:preference:'.$userpreference, 'scormreport_interactions')
+            );
+        }
+    }
+}
index ed66f59..5cd1450 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 $string['pluginname'] = 'Interactions report';
+$string['privacy:metadata:preference:scorm_report_interactions_qtext'] = 'Whether to display the summary of questions in the SCORM interactions report';
+$string['privacy:metadata:preference:scorm_report_interactions_resp'] = 'Whether to display the summary of responses in the SCORM interactions report';
+$string['privacy:metadata:preference:scorm_report_interactions_result'] = 'Whether to display the summary of results in the SCORM interactions report';
+$string['privacy:metadata:preference:scorm_report_interactions_right'] = 'Whether to display the summary of right anwers in the SCORM interactions report';
+$string['privacy:metadata:preference:scorm_report_pagesize'] = 'Number of users to display in the SCORM reports';
 $string['questionx'] = 'Question {$a}';
 $string['responsex'] = 'Response {$a}';
 $string['rightanswerx'] = 'Right answer {$a}';
diff --git a/mod/scorm/report/interactions/tests/privacy_test.php b/mod/scorm/report/interactions/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..80c63d2
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the scormreport_interactions implementation of the privacy API.
+ *
+ * @package    scormreport_interactions
+ * @category   test
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\request\writer;
+use scormreport_interactions\privacy\provider;
+
+/**
+ * Unit tests for the scormreport_interactions implementation of the privacy API.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class scormreport_interactions_privacy_testcase extends \core_privacy\tests\provider_testcase {
+
+    /**
+     * Basic setup for these tests.
+     */
+    public function setUp() {
+        $this->resetAfterTest(true);
+    }
+
+    /**
+     * Ensure that export_user_preferences returns no data if the user has no data.
+     */
+    public function test_export_user_preferences_not_defined() {
+        $user = \core_user::get_user_by_username('admin');
+        provider::export_user_preferences($user->id);
+
+        $writer = writer::with_context(\context_system::instance());
+        $this->assertFalse($writer->has_any_data());
+    }
+
+    /**
+     * Ensure that export_user_preferences returns single preferences.
+     */
+    public function test_export_user_preferences_single() {
+        // Define a user preference.
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        set_user_preference('scorm_report_pagesize', 50);
+        set_user_preference('scorm_report_interactions_qtext', 1);
+        set_user_preference('scorm_report_interactions_resp', 0);
+        set_user_preference('scorm_report_interactions_right', 1);
+        set_user_preference('scorm_report_interactions_result', 1);
+
+        // Validate exported data.
+        provider::export_user_preferences($user->id);
+        $context = \context_user::instance($user->id);
+        $writer = writer::with_context($context);
+        $this->assertTrue($writer->has_any_data());
+        $prefs = $writer->get_user_preferences('scormreport_interactions');
+        $this->assertCount(5, (array) $prefs);
+        $this->assertEquals(
+            get_string('privacy:metadata:preference:scorm_report_pagesize', 'scormreport_interactions'),
+            $prefs->scorm_report_pagesize->description
+        );
+        $this->assertEquals(50, $prefs->scorm_report_pagesize->value);
+        $this->assertEquals(
+            get_string('privacy:metadata:preference:scorm_report_interactions_qtext', 'scormreport_interactions'),
+            $prefs->scorm_report_interactions_qtext->description
+        );
+        $this->assertEquals(get_string('yes'), $prefs->scorm_report_interactions_qtext->value);
+        $this->assertEquals(get_string('no'), $prefs->scorm_report_interactions_resp->value);
+    }
+}
diff --git a/mod/scorm/report/objectives/classes/privacy/provider.php b/mod/scorm/report/objectives/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..48465c7
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for scormreport_objectives.
+ *
+ * @package    scormreport_objectives
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace scormreport_objectives\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+
+/**
+ * Privacy Subsystem for scormreport_objectives.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+    \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\user_preference_provider {
+
+    /**
+     * Returns meta data about this system.
+     *
+     * @param  collection $collection The initialised item collection to add items to.
+     * @return collection A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        // User preferences shared between different scorm reports.
+        $collection->add_user_preference('scorm_report_pagesize', 'privacy:metadata:preference:scorm_report_pagesize');
+
+        // User preferences specific for this scorm report.
+        $collection->add_user_preference(
+            'scorm_report_objectives_score',
+            'privacy:metadata:preference:scorm_report_objectives_score'
+        );
+
+        return $collection;
+    }
+
+    /**
+     * Store all user preferences for the plugin.
+     *
+     * @param  int $userid The userid of the user whose data is to be exported.
+     */
+    public static function export_user_preferences(int $userid) {
+        static::get_and_export_user_preference($userid, 'scorm_report_pagesize');
+        static::get_and_export_user_preference($userid, 'scorm_report_objectives_score', true);
+    }
+
+    /**
+     * Get and export a user preference.
+     *
+     * @param  int     $userid The userid of the user whose data is to be exported.
+     * @param  string  $userpreference The user preference to export.
+     * @param  boolean $transform If true, transform value to yesno.
+     */
+    protected static function get_and_export_user_preference(int $userid, string $userpreference, $transform = false) {
+        $prefvalue = get_user_preferences($userpreference, null, $userid);
+        if ($prefvalue !== null) {
+            if ($transform) {
+                $transformedvalue = transform::yesno($prefvalue);
+            } else {
+                $transformedvalue = $prefvalue;
+            }
+            writer::export_user_preference(
+                'scormreport_objectives',
+                $userpreference,
+                $transformedvalue,
+                get_string('privacy:metadata:preference:'.$userpreference, 'scormreport_objectives')
+            );
+        }
+    }
+}
index 7f2d17f..efbcfcb 100644 (file)
@@ -26,6 +26,8 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['pluginname'] = 'Objectives report';
+$string['privacy:metadata:preference:scorm_report_objectives_score'] = 'Whether to display the objective score in the SCORM report';
+$string['privacy:metadata:preference:scorm_report_pagesize'] = 'Number of users to display in the SCORM reports';
 $string['objectivex'] = 'Objective {$a}';
 $string['objectivescore'] = 'Show objective score';
 $string['score'] = 'score';
diff --git a/mod/scorm/report/objectives/tests/privacy_test.php b/mod/scorm/report/objectives/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..90ae77e
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the scormreport_objectives implementation of the privacy API.
+ *
+ * @package    scormreport_objectives
+ * @category   test
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_privacy\local\request\writer;
+use scormreport_objectives\privacy\provider;
+
+/**
+ * Unit tests for the scormreport_objectives implementation of the privacy API.
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class scormreport_objectives_privacy_testcase extends \core_privacy\tests\provider_testcase {
+
+    /**
+     * Basic setup for these tests.
+     */
+    public function setUp() {
+        $this->resetAfterTest(true);
+    }
+
+    /**
+     * Ensure that export_user_preferences returns no data if the user has no data.
+     */
+    public function test_export_user_preferences_not_defined() {
+        $user = \core_user::get_user_by_username('admin');
+        provider::export_user_preferences($user->id);
+
+        $writer = writer::with_context(\context_system::instance());
+        $this->assertFalse($writer->has_any_data());
+    }
+
+    /**
+     * Ensure that export_user_preferences returns single preferences.
+     */
+    public function test_export_user_preferences_single() {
+        // Define a user preference.
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        set_user_preference('scorm_report_pagesize', 50);
+        set_user_preference('scorm_report_objectives_score', 1);
+
+        // Validate exported data.
+        provider::export_user_preferences($user->id);
+        $context = \context_user::instance($user->id);
+        $writer = writer::with_context($context);
+        $this->assertTrue($writer->has_any_data());
+        $prefs = $writer->get_user_preferences('scormreport_objectives');
+        $this->assertCount(2, (array) $prefs);
+        $this->assertEquals(
+            get_string('privacy:metadata:preference:scorm_report_pagesize', 'scormreport_objectives'),
+            $prefs->scorm_report_pagesize->description
+        );
+        $this->assertEquals(50, $prefs->scorm_report_pagesize->value);
+        $this->assertEquals(
+            get_string('privacy:metadata:preference:scorm_report_objectives_score', 'scormreport_objectives'),
+            $prefs->scorm_report_objectives_score->description
+        );
+        $this->assertEquals(get_string('yes'), $prefs->scorm_report_objectives_score->value);
+    }
+}
diff --git a/mod/scorm/tests/privacy_test.php b/mod/scorm/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..096988d
--- /dev/null
@@ -0,0 +1,243 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Base class for unit tests for mod_scorm.
+ *
+ * @package    mod_scorm
+ * @category   test
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use mod_scorm\privacy\provider;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\writer;
+use core_privacy\tests\provider_testcase;
+
+/**
+ * Unit tests for mod\scorm\classes\privacy\provider.php
+ *
+ * @copyright  2018 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_scorm_testcase extends provider_testcase {
+
+    /** @var stdClass User without any AICC/SCORM attempt. */
+    protected $student0;
+
+    /** @var stdClass User with some AICC/SCORM attempt. */
+    protected $student1;
+
+    /** @var stdClass User with some AICC/SCORM attempt. */
+    protected $student2;
+
+    /** @var context context_module of the SCORM activity. */
+    protected $context;
+
+    /**
+     * Test getting the context for the user ID related to this plugin.
+     */
+    public function test_get_contexts_for_userid() {
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $this->scorm_setup_test_scenario_data();
+
+        // The student0 hasn't any attempt.
+        $contextlist = provider::get_contexts_for_userid($this->student0->id);
+        $this->assertCount(0, (array) $contextlist->get_contextids());
+
+        // The student1 has data in the SCORM context.
+        $contextlist = provider::get_contexts_for_userid($this->student1->id);
+        $this->assertCount(1, (array) $contextlist->get_contextids());
+        $this->assertContains($this->context->id, $contextlist->get_contextids());
+    }
+
+    /**
+     * Test that data is exported correctly for this plugin.
+     */
+    public function test_export_user_data() {
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $this->scorm_setup_test_scenario_data();
+
+        // Validate exported data for student0 (without any AICC/SCORM attempt).
+        $this->setUser($this->student0);
+        $writer = writer::with_context($this->context);
+        $this->export_context_data_for_user($this->student0->id, $this->context, 'mod_scorm');
+        $data = $writer->get_related_data([], 'attempt-1');
+        $this->assertEmpty($data);
+        $this->export_context_data_for_user($this->student0->id, $this->context, 'mod_scorm');
+        $data = $writer->get_related_data([], 'aiccsession');
+        $this->assertEmpty($data);
+
+        // Validate exported data for student1.
+        writer::reset();
+        $this->setUser($this->student1);
+        $writer = writer::with_context($this->context);
+        $this->assertFalse($writer->has_any_data());
+        $this->export_context_data_for_user($this->student1->id, $this->context, 'mod_scorm');
+        $data = $writer->get_related_data([], 'attempt-1');
+        $this->assertCount(1, (array) $data);
+        $this->assertCount(2, (array) reset($data));
+        $data = $writer->get_related_data([], 'attempt-2');
+        $this->assertCount(2, (array) reset($data));
+        // The student1 has only 2 scoes_track attempts.
+        $data = $writer->get_related_data([], 'attempt-3');
+        $this->assertEmpty($data);
+        // The student1 has only 1 aicc_session.
+        $this->export_context_data_for_user($this->student1->id, $this->context, 'mod_scorm');
+        $data = $writer->get_related_data([], 'aiccsession');
+        $this->assertCount(1, (array) $data);
+    }
+
+    /**
+     * Test for provider::delete_data_for_all_users_in_context().
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $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 data based on the context.
+        provider::delete_data_for_all_users_in_context($this->context);
+
+        // After deletion, the scorm_scoes_track entries should have been deleted.
+        $count = $DB->count_records('scorm_scoes_track');
+        $this->assertEquals(0, $count);
+        // After deletion, the scorm_aicc_session entries should have been deleted.
+        $count = $DB->count_records('scorm_aicc_session');
+        $this->assertEquals(0, $count);
+    }
+
+    /**
+     * Test for provider::delete_data_for_user().
+     */
+    public function test_delete_data_for_user() {
+        global $DB;
+
+        $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);
+
+        $approvedcontextlist = new approved_contextlist($this->student1, 'scorm', [$this->context->id]);
+        provider::delete_data_for_user($approvedcontextlist);
+
+        // 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).
+        $approvedcontextlist = new approved_contextlist($this->student0, 'scorm', [$this->context->id]);
+        provider::delete_data_for_user($approvedcontextlist);
+        $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.
+     */
+    protected function scorm_setup_test_scenario_data() {
+        global $DB;
+
+        set_config('allowaicchacp', 1, 'scorm');
+
+        // Setup test data.
+        $course = $this->getDataGenerator()->create_course();
+        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id));
+        $this->context = \context_module::instance($scorm->cmid);
+
+        // Users enrolments.
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+
+        // Create student0 withot any SCORM attempt.
+        $this->student0 = self::getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($this->student0->id, $course->id, $studentrole->id, 'manual');
+
+        // Create student1 with 2 SCORM attempts and 1 AICC session.
+        $this->student1 = self::getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($this->student1->id, $course->id, $studentrole->id, 'manual');
+        static::scorm_insert_attempt($scorm, $this->student1->id, 1);
+        static::scorm_insert_attempt($scorm, $this->student1->id, 2);
+
+        // Create student2 with 2 SCORM attempts and 1 AICC session.
+        $this->student2 = self::getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($this->student2->id, $course->id, $studentrole->id, 'manual');
+        static::scorm_insert_attempt($scorm, $this->student2->id, 1);
+        static::scorm_insert_attempt($scorm, $this->student2->id, 2);
+    }
+
+    /**
+     * Create a SCORM attempt.
+     *
+     * @param  object $scorm SCORM activity.
+     * @param  int $userid  Userid who is doing the attempt.
+     * @param  int $attempt Number of attempt.
+     */
+    protected function scorm_insert_attempt($scorm, $userid, $attempt) {
+        global $DB;
+
+        $newattempt = 'on';
+        $mode = 'normal';
+        scorm_check_mode($scorm, $newattempt, $attempt, $userid, $mode);
+        $scoes = scorm_get_scoes($scorm->id);
+        $sco = array_pop($scoes);
+        scorm_insert_track($userid, $scorm->id, $sco->id, $attempt, 'cmi.core.lesson_status', 'completed');
+        scorm_insert_track($userid, $scorm->id, $sco->id, $attempt, 'cmi.score.min', '0');
+        $now = time();
+        $hacpsession = [
+            'scormid' => $scorm->id,
+            'attempt' => $attempt,
+            'hacpsession' => random_string(20),
+            'userid' => $userid,
+            'timecreated' => $now,
+            'timemodified' => $now
+        ];
+        $DB->insert_record('scorm_aicc_session', $hacpsession);
+    }
+}