Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace mod_h5pactivity\privacy; 18 19 use core_privacy\local\metadata\collection; 20 use core_privacy\local\request\approved_contextlist; 21 use core_privacy\local\request\approved_userlist; 22 use core_privacy\local\request\contextlist; 23 use core_privacy\local\request\helper; 24 use core_privacy\local\request\transform; 25 use core_privacy\local\request\userlist; 26 use core_privacy\local\request\writer; 27 use stdClass; 28 29 /** 30 * Privacy API implementation for the H5P activity plugin. 31 * 32 * @package mod_h5pactivity 33 * @category privacy 34 * @copyright 2020 Ferran Recio <ferran@moodle.com> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class provider implements 38 \core_privacy\local\metadata\provider, 39 \core_privacy\local\request\core_userlist_provider, 40 \core_privacy\local\request\plugin\provider { 41 42 /** 43 * Return the fields which contain personal data. 44 * 45 * @param collection $collection The initialised collection to add items to. 46 * @return collection A listing of user data stored through this system. 47 */ 48 public static function get_metadata(collection $collection) : collection { 49 $collection->add_database_table('h5pactivity_attempts', [ 50 'userid' => 'privacy:metadata:userid', 51 'attempt' => 'privacy:metadata:attempt', 52 'timecreated' => 'privacy:metadata:timecreated', 53 'timemodified' => 'privacy:metadata:timemodified', 54 'rawscore' => 'privacy:metadata:rawscore', 55 ], 'privacy:metadata:xapi_track'); 56 57 $collection->add_database_table('h5pactivity_attempts_results', [ 58 'attempt' => 'privacy:metadata:attempt', 59 'timecreated' => 'privacy:metadata:timecreated', 60 'rawscore' => 'privacy:metadata:rawscore', 61 ], 'privacy:metadata:xapi_track_results'); 62 63 $collection->add_subsystem_link('core_xapi', [], 'privacy:metadata:xapisummary'); 64 65 return $collection; 66 } 67 68 /** 69 * Get the list of contexts that contain user information for the specified user. 70 * 71 * @param int $userid The user to search. 72 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 73 */ 74 public static function get_contexts_for_userid(int $userid) : contextlist { 75 $sql = "SELECT ctx.id 76 FROM {h5pactivity_attempts} ss 77 JOIN {modules} m 78 ON m.name = :activityname 79 JOIN {course_modules} cm 80 ON cm.instance = ss.h5pactivityid 81 AND cm.module = m.id 82 JOIN {context} ctx 83 ON ctx.instanceid = cm.id 84 AND ctx.contextlevel = :modlevel 85 WHERE ss.userid = :userid"; 86 87 $params = ['activityname' => 'h5pactivity', 'modlevel' => CONTEXT_MODULE, 'userid' => $userid]; 88 $contextlist = new contextlist(); 89 $contextlist->add_from_sql($sql, $params); 90 91 \core_xapi\privacy\provider::add_contexts_for_userid($contextlist, $userid, 'mod_h5pactivity'); 92 93 return $contextlist; 94 } 95 96 /** 97 * Get the list of users who have data within a context. 98 * 99 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 100 */ 101 public static function get_users_in_context(userlist $userlist) { 102 $context = $userlist->get_context(); 103 104 if (!is_a($context, \context_module::class)) { 105 return; 106 } 107 108 $sql = "SELECT ss.userid 109 FROM {h5pactivity_attempts} ss 110 JOIN {modules} m 111 ON m.name = 'h5pactivity' 112 JOIN {course_modules} cm 113 ON cm.instance = ss.h5pactivityid 114 AND cm.module = m.id 115 JOIN {context} ctx 116 ON ctx.instanceid = cm.id 117 AND ctx.contextlevel = :modlevel 118 WHERE ctx.id = :contextid"; 119 120 $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id]; 121 122 $userlist->add_from_sql('userid', $sql, $params); 123 124 \core_xapi\privacy\provider::add_userids_for_context($userlist); 125 } 126 127 /** 128 * Export all user data for the specified user, in the specified contexts. 129 * 130 * @param approved_contextlist $contextlist The approved contexts to export information for. 131 */ 132 public static function export_user_data(approved_contextlist $contextlist) { 133 global $DB; 134 135 // Remove contexts different from CONTEXT_MODULE. 136 $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) { 137 if ($context->contextlevel == CONTEXT_MODULE) { 138 $carry[] = $context->id; 139 } 140 return $carry; 141 }, []); 142 143 if (empty($contexts)) { 144 return; 145 } 146 147 $user = $contextlist->get_user(); 148 $userid = $user->id; 149 // Get H5P attempts data. 150 foreach ($contexts as $contextid) { 151 $context = \context::instance_by_id($contextid); 152 $data = helper::get_context_data($context, $user); 153 writer::with_context($context)->export_data([], $data); 154 helper::export_context_files($context, $user); 155 156 // Get user's xAPI state data for the particular context. 157 $state = \core_xapi\privacy\provider::get_xapi_states_for_user($contextlist->get_user()->id, 158 'mod_h5pactivity', $context->instanceid); 159 if ($state) { 160 // If the activity has xAPI state data by the user, include it in the export. 161 writer::with_context($context)->export_data( 162 [get_string('privacy:xapistate', 'core_xapi')], (object) $state); 163 } 164 165 } 166 167 // Get attempts track data. 168 list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED); 169 $sql = "SELECT har.id, 170 ha.attempt, 171 har.description, 172 har.interactiontype, 173 har.response, 174 har.additionals, 175 har.rawscore, 176 har.maxscore, 177 har.duration, 178 har.timecreated, 179 ctx.id as contextid 180 FROM {h5pactivity_attempts_results} har 181 JOIN {h5pactivity_attempts} ha 182 ON har.attemptid = ha.id 183 JOIN {course_modules} cm 184 ON cm.instance = ha.h5pactivityid 185 JOIN {context} ctx 186 ON ctx.instanceid = cm.id 187 WHERE ctx.id $insql 188 AND ha.userid = :userid"; 189 $params = array_merge($inparams, ['userid' => $userid]); 190 191 $alldata = []; 192 $attemptsdata = $DB->get_recordset_sql($sql, $params); 193 foreach ($attemptsdata as $track) { 194 $alldata[$track->contextid][$track->attempt][] = (object)[ 195 'description' => $track->description, 196 'response' => $track->response, 197 'interactiontype' => $track->interactiontype, 198 'additionals' => $track->additionals, 199 'rawscore' => $track->rawscore, 200 'maxscore' => $track->maxscore, 201 'duration' => $track->duration, 202 'timecreated' => transform::datetime($track->timecreated), 203 ]; 204 } 205 $attemptsdata->close(); 206 207 // The result data is organised in: 208 // {Course name}/{H5P activity name}/{My attempts}/{Attempt X}/data.json 209 // where X is the attempt number. 210 array_walk($alldata, function($attemptsdata, $contextid) { 211 $context = \context::instance_by_id($contextid); 212 array_walk($attemptsdata, function($data, $attempt) use ($context) { 213 $subcontext = [ 214 get_string('myattempts', 'mod_h5pactivity'), 215 get_string('attempt', 'mod_h5pactivity'). " $attempt" 216 ]; 217 writer::with_context($context)->export_data( 218 $subcontext, 219 (object)['results' => $data] 220 ); 221 }); 222 }); 223 } 224 225 /** 226 * Delete all user data which matches the specified context. 227 * 228 * @param \context $context A user context. 229 */ 230 public static function delete_data_for_all_users_in_context(\context $context) { 231 // This should not happen, but just in case. 232 if ($context->contextlevel != CONTEXT_MODULE) { 233 return; 234 } 235 236 $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid); 237 if (!$cm) { 238 // Only h5pactivity module will be handled. 239 return; 240 } 241 242 self::delete_all_attempts($cm); 243 244 // Delete xAPI state data. 245 \core_xapi\privacy\provider::delete_states_for_all_users($context, 'mod_h5pactivity'); 246 247 } 248 249 /** 250 * Delete all user data for the specified user, in the specified contexts. 251 * 252 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 253 */ 254 public static function delete_data_for_user(approved_contextlist $contextlist) { 255 256 foreach ($contextlist as $context) { 257 if ($context->contextlevel != CONTEXT_MODULE) { 258 continue; 259 } 260 261 $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid); 262 if (!$cm) { 263 // Only h5pactivity module will be handled. 264 continue; 265 } 266 267 $user = $contextlist->get_user(); 268 269 self::delete_all_attempts($cm, $user); 270 271 // Delete xAPI state data. 272 \core_xapi\privacy\provider::delete_states_for_user($contextlist, 'mod_h5pactivity'); 273 } 274 } 275 276 /** 277 * Delete multiple users within a single context. 278 * 279 * @param approved_userlist $userlist The approved context and user information to delete information for. 280 */ 281 public static function delete_data_for_users(approved_userlist $userlist) { 282 283 $context = $userlist->get_context(); 284 285 if (!is_a($context, \context_module::class)) { 286 return; 287 } 288 289 $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid); 290 if (!$cm) { 291 // Only h5pactivity module will be handled. 292 return; 293 } 294 295 $userids = $userlist->get_userids(); 296 297 foreach ($userids as $userid) { 298 self::delete_all_attempts ($cm, (object)['id' => $userid]); 299 } 300 301 // Delete xAPI states data. 302 \core_xapi\privacy\provider::delete_states_for_userlist($userlist); 303 304 } 305 306 /** 307 * Wipe all attempt data for specific course_module and an optional user. 308 * 309 * @param stdClass $cm a course_module record 310 * @param stdClass $user a user record 311 */ 312 private static function delete_all_attempts(stdClass $cm, stdClass $user = null): void { 313 global $DB; 314 315 $where = 'a.h5pactivityid = :h5pactivityid'; 316 $conditions = ['h5pactivityid' => $cm->instance]; 317 if (!empty($user)) { 318 $where .= ' AND a.userid = :userid'; 319 $conditions['userid'] = $user->id; 320 } 321 322 $DB->delete_records_select('h5pactivity_attempts_results', "attemptid IN ( 323 SELECT a.id 324 FROM {h5pactivity_attempts} a 325 WHERE $where)", $conditions); 326 327 $DB->delete_records('h5pactivity_attempts', $conditions); 328 } 329 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body