mirror of
https://gh.llkk.cc/https://github.com/h5p/moodle-mod_hvp.git
synced 2026-03-04 13:36:41 +08:00
519 lines
18 KiB
PHP
519 lines
18 KiB
PHP
<?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 H5P.
|
|
*/
|
|
|
|
namespace mod_hvp\privacy;
|
|
|
|
use \core_privacy\local\request\writer;
|
|
use \core_privacy\local\request\contextlist;
|
|
use \core_privacy\local\request\approved_contextlist;
|
|
use \core_privacy\local\request\deletion_criteria;
|
|
use \core_privacy\local\request\approved_userlist;
|
|
use \core_privacy\local\request\userlist;
|
|
use \core_privacy\local\metadata\collection;
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
/**
|
|
* Privacy Subsystem implementation for H5P.
|
|
*/
|
|
class provider implements
|
|
// This plugin has data.
|
|
\core_privacy\local\metadata\provider,
|
|
\core_privacy\local\request\core_userlist_provider,
|
|
// This plugin currently implements the original plugin_provider interface.
|
|
\core_privacy\local\request\plugin\provider {
|
|
|
|
use \core_privacy\local\legacy_polyfill;
|
|
|
|
/**
|
|
* Get the list of contexts that contain user information for the specified user.
|
|
*
|
|
* @param collection $items The collection to add metadata to.
|
|
*
|
|
* @return collection The array of metadata
|
|
*/
|
|
public static function _get_metadata(collection $items) {
|
|
// Stores files using the Moodle file api.
|
|
$items->add_subsystem_link(
|
|
'core_files',
|
|
[],
|
|
'privacy:metadata:core_files'
|
|
);
|
|
|
|
// Stores grades using the Moodle gradebook api.
|
|
$items->add_subsystem_link(
|
|
'core_grades',
|
|
[],
|
|
'privacy:metadata:core_grades'
|
|
);
|
|
|
|
// Content user data table.
|
|
$items->add_database_table('hvp_content_user_data', [
|
|
'id' => 'privacy:metadata:hvp_content_user_data:id',
|
|
'user_id' => 'privacy:metadata:hvp_content_user_data:user_id',
|
|
'hvp_id' => 'privacy:metadata:hvp_content_user_data:hvp_id',
|
|
'sub_content_id' => 'privacy:metadata:hvp_content_user_data:sub_content_id',
|
|
'data_id' => 'privacy:metadata:hvp_content_user_data:data_id',
|
|
'data' => 'privacy:metadata:hvp_content_user_data:data',
|
|
'preloaded' => 'privacy:metadata:hvp_content_user_data:preloaded',
|
|
'delete_on_content_change' => 'privacy:metadata:hvp_content_user_data:delete_on_content_change',
|
|
],
|
|
'privacy:metadata:hvp_content_user_data'
|
|
);
|
|
|
|
// Events table.
|
|
$items->add_database_table('hvp_events', [
|
|
'id' => 'privacy:metadata:hvp_events:id',
|
|
'user_id' => 'privacy:metadata:hvp_events:user_id',
|
|
'created_at' => 'privacy:metadata:hvp_events:created_at',
|
|
'type' => 'privacy:metadata:hvp_events:type',
|
|
'sub_type' => 'privacy:metadata:hvp_events:sub_type',
|
|
'content_id' => 'privacy:metadata:hvp_events:content_id',
|
|
'content_title' => 'privacy:metadata:hvp_events:content_title',
|
|
'library_name' => 'privacy:metadata:hvp_events:library_name',
|
|
'library_version' => 'privacy:metadata:hvp_events:library_version',
|
|
], 'privacy:metadata:hvp_events');
|
|
|
|
// Xapi results table.
|
|
$items->add_database_table('hvp_xapi_results', [
|
|
'id' => 'privacy:metadata:hvp_xapi_results:id',
|
|
'content_id' => 'privacy:metadata:hvp_xapi_results:content_id',
|
|
'user_id' => 'privacy:metadata:hvp_xapi_results:user_id',
|
|
'parent_id' => 'privacy:metadata:hvp_xapi_results:parent_id',
|
|
'interaction_type' => 'privacy:metadata:hvp_xapi_results:interaction_type',
|
|
'description' => 'privacy:metadata:hvp_xapi_results:description',
|
|
'correct_responses_pattern' => 'privacy:metadata:hvp_xapi_results:correct_responses_pattern',
|
|
'response' => 'privacy:metadata:hvp_xapi_results:response',
|
|
'additionals' => 'privacy:metadata:hvp_xapi_results:additionals',
|
|
'raw_score' => 'privacy:metadata:hvp_xapi_results:raw_score',
|
|
'max_score' => 'privacy:metadata:hvp_xapi_results:max_score',
|
|
], 'privacy:metadata:hvp_xapi_results');
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Get the list of contexts where the specified user has attempted a quiz, or been involved with manual marking
|
|
* and/or grading of a quiz.
|
|
*
|
|
* @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($userid) {
|
|
$contextlist = new contextlist();
|
|
|
|
// Context for content_user_data.
|
|
$cudsql = "
|
|
SELECT
|
|
c.id
|
|
FROM {course_modules} cm
|
|
INNER JOIN {modules} m ON m.id = cm.module
|
|
INNER JOIN {context} c ON c.instanceid = cm.id
|
|
INNER JOIN {hvp} h ON h.id = cm.instance
|
|
INNER JOIN {hvp_content_user_data} d ON h.id = d.hvp_id
|
|
WHERE m.name = 'hvp'
|
|
AND c.contextlevel = :contextlevel
|
|
AND d.user_id = :userid
|
|
";
|
|
|
|
$cudparams = array(
|
|
'contextlevel' => CONTEXT_MODULE,
|
|
'userid' => $userid,
|
|
);
|
|
|
|
$contextlist->add_from_sql($cudsql, $cudparams);
|
|
|
|
// Context for xapi results.
|
|
$xapisql = "
|
|
SELECT
|
|
c.id
|
|
FROM {course_modules} cm
|
|
INNER JOIN {modules} m ON m.id = cm.module
|
|
INNER JOIN {context} c ON c.instanceid = cm.id
|
|
INNER JOIN {hvp} h ON h.id = cm.instance
|
|
INNER JOIN {hvp_xapi_results} x ON x.content_id = h.id
|
|
WHERE m.name = 'hvp'
|
|
AND c.contextlevel = :contextlevel
|
|
AND x.user_id = :userid
|
|
";
|
|
|
|
$xapiparams = array(
|
|
'contextlevel' => CONTEXT_MODULE,
|
|
'userid' => $userid,
|
|
);
|
|
|
|
$contextlist->add_from_sql($xapisql, $xapiparams);
|
|
|
|
// Table hvp_events note:
|
|
// H5P events are not tied to instance ids, thus we cannot determine
|
|
// their context ids, they are considered to be user level context
|
|
// actions.
|
|
|
|
return $contextlist;
|
|
}
|
|
|
|
/**
|
|
* Export all user data for the specified user, in the specified contexts, using the supplied
|
|
* exporter instance.
|
|
*
|
|
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
|
*/
|
|
public static function _export_user_data(approved_contextlist $contextlist) {
|
|
global $DB;
|
|
|
|
if (!count($contextlist)) {
|
|
return;
|
|
}
|
|
|
|
$user = $contextlist->get_user();
|
|
$userid = $user->id;
|
|
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
|
|
|
$cud = self::get_exportable_content_user_data($contextsql, $contextparams, $userid);
|
|
$xapi = self::get_exportable_xapi_results($contextsql, $contextparams, $userid);
|
|
|
|
// Export data with context.
|
|
foreach ($contextlist->get_contexts() as $context) {
|
|
$h5pdata = \core_privacy\local\request\helper::get_context_data($context, $contextlist->get_user());
|
|
\core_privacy\local\request\helper::export_context_files($context, $contextlist->get_user());
|
|
|
|
// Add content user data.
|
|
if (!empty($cud[$context->id]) && !empty($cud[$context->id]->data)) {
|
|
$h5pdata->contentuserdata = $cud[$context->id]->data;
|
|
}
|
|
|
|
// Add xAPI data.
|
|
if (!empty($xapi[$context->id])) {
|
|
$h5pdata->xapiresults = $xapi[$context->id];
|
|
}
|
|
|
|
writer::with_context($context)->export_data([], $h5pdata);
|
|
}
|
|
|
|
// Write H5PEvents to subcontext of the user context.
|
|
$usercontext = \context_user::instance($userid);
|
|
$h5pevents = self::get_exportable_events($userid);
|
|
writer::with_context($usercontext)->export_data(['H5PEvents'], $h5pevents);
|
|
}
|
|
|
|
/**
|
|
* Get exportable content user data for a given context and user
|
|
*
|
|
* @param $contextsql
|
|
* @param $contextparams
|
|
* @param $userid
|
|
*
|
|
* @return array Exportable and writable content user data
|
|
* @throws \dml_exception
|
|
*/
|
|
protected static function get_exportable_content_user_data($contextsql, $contextparams, $userid) {
|
|
global $DB;
|
|
|
|
$cudsql = "
|
|
SELECT
|
|
c.id as contextid,
|
|
cm.id as cmid,
|
|
h.id,
|
|
h.name,
|
|
cud.data
|
|
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 {hvp} h ON h.id = cm.instance
|
|
INNER JOIN {hvp_content_user_data} cud ON cud.hvp_id = h.id
|
|
WHERE cud.user_id = :userid
|
|
AND c.id {$contextsql}
|
|
";
|
|
|
|
$cudparams = [
|
|
'contextlevel' => CONTEXT_MODULE,
|
|
'modname' => 'hvp',
|
|
'userid' => $userid,
|
|
];
|
|
$cudparams += $contextparams;
|
|
|
|
$cudresult = $DB->get_recordset_sql($cudsql, $cudparams);
|
|
$cud = [];
|
|
foreach ($cudresult as $record) {
|
|
$cud[$record->contextid] = $record;
|
|
}
|
|
$cudresult->close();
|
|
|
|
return $cud;
|
|
}
|
|
|
|
/**
|
|
* Get exportable xapi results from context and user id
|
|
*
|
|
* @param $contextsql
|
|
* @param $contextparams
|
|
* @param $userid
|
|
*
|
|
* @return array Exportable and writable xapi results
|
|
* @throws \dml_exception
|
|
*/
|
|
protected static function get_exportable_xapi_results($contextsql, $contextparams, $userid) {
|
|
global $DB;
|
|
|
|
$xapisql = "
|
|
SELECT
|
|
c.id as contextid,
|
|
cm.id as cmid,
|
|
h.id,
|
|
h.name,
|
|
x.content_id,
|
|
x.parent_id,
|
|
x.description,
|
|
x.response,
|
|
x.additionals,
|
|
x.raw_score,
|
|
x.max_score
|
|
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 {hvp} h ON h.id = cm.instance
|
|
INNER JOIN {hvp_xapi_results} x ON x.content_id = h.id
|
|
WHERE x.user_id = :userid
|
|
AND c.id {$contextsql}
|
|
";
|
|
|
|
$xapiparams = [
|
|
'contextlevel' => CONTEXT_MODULE,
|
|
'modname' => 'hvp',
|
|
'userid' => $userid,
|
|
];
|
|
$xapiparams += $contextparams;
|
|
|
|
$xapiresult = $DB->get_recordset_sql($xapisql, $xapiparams);
|
|
$xapi = [];
|
|
foreach ($xapiresult as $record) {
|
|
$h5pxapi = (object) array();
|
|
if (!empty($record->content_id)) {
|
|
$h5pxapi->content_id = $record->content_id;
|
|
}
|
|
|
|
if (!empty($record->parent_id)) {
|
|
$h5pxapi->parent_id = $record->parent_id;
|
|
}
|
|
|
|
if (!empty($record->description)) {
|
|
$h5pxapi->description = $record->description;
|
|
}
|
|
|
|
if (!empty($record->response)) {
|
|
$h5pxapi->response = $record->response;
|
|
}
|
|
|
|
if (!empty($record->additionals)) {
|
|
$h5pxapi->additionals = $record->additionals;
|
|
}
|
|
|
|
if (!empty($record->raw_score)) {
|
|
$h5pxapi->raw_score = $record->raw_score;
|
|
}
|
|
|
|
if (!empty($record->max_score)) {
|
|
$h5pxapi->max_score = $record->max_score;
|
|
}
|
|
|
|
$xapi[$record->contextid][] = $h5pxapi;
|
|
}
|
|
$xapiresult->close();
|
|
|
|
return $xapi;
|
|
}
|
|
|
|
/**
|
|
* Get exportable H5P events from user id
|
|
*
|
|
* @param $userid
|
|
*
|
|
* @return object Exportable and writable H5P events
|
|
* @throws \dml_exception
|
|
*/
|
|
protected static function get_exportable_events($userid) {
|
|
global $DB;
|
|
|
|
$eventssql = "
|
|
SELECT *
|
|
FROM {hvp_events}
|
|
WHERE user_id = :userid
|
|
";
|
|
|
|
$eventsparams = [
|
|
'userid' => $userid,
|
|
];
|
|
|
|
$h5peventsresults = $DB->get_recordset_sql($eventssql, $eventsparams);
|
|
$h5pevents = (object) [
|
|
'events' => [],
|
|
];
|
|
foreach ($h5peventsresults as $event) {
|
|
$h5pevents->events[] = $event;
|
|
}
|
|
$h5peventsresults->close();
|
|
|
|
return $h5pevents;
|
|
}
|
|
|
|
/**
|
|
* Delete all data for all users in the specified context.
|
|
*
|
|
* @param \context $context The specific context to delete data for.
|
|
*/
|
|
public static function _delete_data_for_all_users_in_context(\context $context) {
|
|
global $DB;
|
|
|
|
if ($context->contextlevel == CONTEXT_USER) {
|
|
// Delete all H5P events.
|
|
$DB->delete_records('hvp_events');
|
|
return;
|
|
} else if ($context->contextlevel != CONTEXT_MODULE) {
|
|
return;
|
|
}
|
|
|
|
$cm = get_coursemodule_from_id('hvp', $context->instanceid);
|
|
if (!$cm) {
|
|
return;
|
|
}
|
|
|
|
// Delete content user data.
|
|
$DB->delete_records('hvp_content_user_data', [
|
|
'hvp_id' => $cm->instance,
|
|
]);
|
|
|
|
// Delete xAPI results.
|
|
$DB->delete_records('hvp_xapi_results', [
|
|
'content_id' => $cm->instance,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
$count = $contextlist->count();
|
|
if (empty($count)) {
|
|
return;
|
|
}
|
|
|
|
$userid = $contextlist->get_user()->id;
|
|
foreach ($contextlist->get_contexts() as $context) {
|
|
$cm = get_coursemodule_from_id('hvp', $context->instanceid);
|
|
|
|
if (!$cm) {
|
|
continue;
|
|
}
|
|
|
|
// Delete content user data.
|
|
$DB->delete_records('hvp_content_user_data', [
|
|
'hvp_id' => $cm->instance,
|
|
'user_id' => $userid,
|
|
]);
|
|
|
|
// Delete xAPI results.
|
|
$DB->delete_records('hvp_xapi_results', [
|
|
'content_id' => $cm->instance,
|
|
'user_id' => $userid,
|
|
]);
|
|
}
|
|
|
|
// Delete H5P events.
|
|
$DB->delete_records('hvp_events', [
|
|
'user_id' => $userid,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 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 d.user_id
|
|
FROM {course_modules} cm
|
|
INNER JOIN {modules} m ON m.id = cm.module AND m.name = 'hvp'
|
|
INNER JOIN {context} c ON c.instanceid = cm.id
|
|
INNER JOIN {hvp} h ON h.id = cm.instance
|
|
INNER JOIN {hvp_content_user_data} d ON h.id = d.hvp_id
|
|
WHERE c.contextlevel = :contextlevel AND c.id = :contextid";
|
|
|
|
$params = [
|
|
'contextlevel' => CONTEXT_MODULE,
|
|
'contextid' => $context->id,
|
|
];
|
|
$userlist->add_from_sql('user_id', $sql, $params);
|
|
|
|
$sql = "
|
|
SELECT
|
|
x.user_id
|
|
FROM {course_modules} cm
|
|
INNER JOIN {modules} m ON m.id = cm.module AND m.name = 'hvp'
|
|
INNER JOIN {context} c ON c.instanceid = cm.id
|
|
INNER JOIN {hvp} h ON h.id = cm.instance
|
|
INNER JOIN {hvp_xapi_results} x ON x.content_id = h.id
|
|
WHERE c.contextlevel = :contextlevel AND c.id = :contextid";
|
|
$userlist->add_from_sql('user_id', $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;
|
|
}
|
|
$cm = get_coursemodule_from_id('hvp', $context->instanceid);
|
|
|
|
// Prepare SQL to gather all completed IDs.
|
|
$userids = $userlist->get_userids();
|
|
list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
|
|
|
$inparams['hvpid'] = $cm->instance;
|
|
|
|
$DB->delete_records_select(
|
|
'hvp_content_user_data',
|
|
"hvp_id = :hvpid AND user_id $insql",
|
|
$inparams
|
|
);
|
|
|
|
$DB->delete_records_select(
|
|
'hvp_xapi_results',
|
|
"hvp_id = :hvpid AND user_id $insql",
|
|
$inparams
|
|
);
|
|
|
|
}
|
|
}
|