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 = "SELECTc.idFROM {course_modules} cmINNER JOIN {modules} m ON m.id = cm.moduleINNER JOIN {context} c ON c.instanceid = cm.idINNER JOIN {hvp} h ON h.id = cm.instanceINNER JOIN {hvp_content_user_data} d ON h.id = d.hvp_idWHERE m.name = 'hvp'AND c.contextlevel = :contextlevelAND d.user_id = :userid";$cudparams = array('contextlevel' => CONTEXT_MODULE,'userid' => $userid,);$contextlist->add_from_sql($cudsql, $cudparams);// Context for xapi results.$xapisql = "SELECTc.idFROM {course_modules} cmINNER JOIN {modules} m ON m.id = cm.moduleINNER JOIN {context} c ON c.instanceid = cm.idINNER JOIN {hvp} h ON h.id = cm.instanceINNER JOIN {hvp_xapi_results} x ON x.content_id = h.idWHERE m.name = 'hvp'AND c.contextlevel = :contextlevelAND 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 = "SELECTc.id as contextid,cm.id as cmid,h.id,h.name,cud.dataFROM {context} cINNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelINNER JOIN {modules} m ON m.id = cm.module AND m.name = :modnameINNER JOIN {hvp} h ON h.id = cm.instanceINNER JOIN {hvp_content_user_data} cud ON cud.hvp_id = h.idWHERE cud.user_id = :useridAND 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 = "SELECTc.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_scoreFROM {context} cINNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelINNER JOIN {modules} m ON m.id = cm.module AND m.name = :modnameINNER JOIN {hvp} h ON h.id = cm.instanceINNER JOIN {hvp_xapi_results} x ON x.content_id = h.idWHERE x.user_id = :useridAND 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_idFROM {course_modules} cmINNER JOIN {modules} m ON m.id = cm.module AND m.name = 'hvp'INNER JOIN {context} c ON c.instanceid = cm.idINNER JOIN {hvp} h ON h.id = cm.instanceINNER JOIN {hvp_content_user_data} d ON h.id = d.hvp_idWHERE c.contextlevel = :contextlevel AND c.id = :contextid";$params = ['contextlevel' => CONTEXT_MODULE,'contextid' => $context->id,];$userlist->add_from_sql('user_id', $sql, $params);$sql = "SELECTx.user_idFROM {course_modules} cmINNER JOIN {modules} m ON m.id = cm.module AND m.name = 'hvp'INNER JOIN {context} c ON c.instanceid = cm.idINNER JOIN {hvp} h ON h.id = cm.instanceINNER JOIN {hvp_xapi_results} x ON x.content_id = h.idWHERE 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);}}