AutorÃa | Ultima modificación | Ver Log |
<?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
);
}
}