Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
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 /** 18 * Privacy class for requesting user data. 19 * 20 * @package mod_scorm 21 * @copyright 2018 Sara Arjona <sara@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace mod_scorm\privacy; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 use core_privacy\local\metadata\collection; 30 use core_privacy\local\request\approved_contextlist; 31 use core_privacy\local\request\approved_userlist; 32 use core_privacy\local\request\contextlist; 33 use core_privacy\local\request\helper; 34 use core_privacy\local\request\transform; 35 use core_privacy\local\request\userlist; 36 use core_privacy\local\request\writer; 37 38 /** 39 * Privacy class for requesting user data. 40 * 41 * @copyright 2018 Sara Arjona <sara@moodle.com> 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 class provider implements 45 \core_privacy\local\metadata\provider, 46 \core_privacy\local\request\core_userlist_provider, 47 \core_privacy\local\request\plugin\provider { 48 49 /** 50 * Return the fields which contain personal data. 51 * 52 * @param collection $collection The initialised collection to add items to. 53 * @return collection A listing of user data stored through this system. 54 */ 55 public static function get_metadata(collection $collection) : collection { 56 $collection->add_database_table('scorm_attempt', [ 57 'userid' => 'privacy:metadata:userid', 58 'attempt' => 'privacy:metadata:attempt', 59 ], 'privacy:metadata:scorm_attempt'); 60 61 $collection->add_database_table('scorm_aicc_session', [ 62 'userid' => 'privacy:metadata:userid', 63 'scormmode' => 'privacy:metadata:aicc_session:scormmode', 64 'scormstatus' => 'privacy:metadata:aicc_session:scormstatus', 65 'attempt' => 'privacy:metadata:attempt', 66 'lessonstatus' => 'privacy:metadata:aicc_session:lessonstatus', 67 'sessiontime' => 'privacy:metadata:aicc_session:sessiontime', 68 'timecreated' => 'privacy:metadata:aicc_session:timecreated', 69 'timemodified' => 'privacy:metadata:timemodified', 70 ], 'privacy:metadata:scorm_aicc_session'); 71 72 $collection->add_external_location_link('aicc', [ 73 'data' => 'privacy:metadata:aicc:data' 74 ], 'privacy:metadata:aicc:externalpurpose'); 75 76 return $collection; 77 } 78 79 /** 80 * Get the list of contexts that contain user information for the specified user. 81 * 82 * @param int $userid The user to search. 83 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 84 */ 85 public static function get_contexts_for_userid(int $userid) : contextlist { 86 $sql = "SELECT ctx.id 87 FROM {%s} ss 88 JOIN {modules} m 89 ON m.name = 'scorm' 90 JOIN {course_modules} cm 91 ON cm.instance = ss.scormid 92 AND cm.module = m.id 93 JOIN {context} ctx 94 ON ctx.instanceid = cm.id 95 AND ctx.contextlevel = :modlevel 96 WHERE ss.userid = :userid"; 97 98 $params = ['modlevel' => CONTEXT_MODULE, 'userid' => $userid]; 99 $contextlist = new contextlist(); 100 $contextlist->add_from_sql(sprintf($sql, 'scorm_attempt'), $params); 101 $contextlist->add_from_sql(sprintf($sql, 'scorm_aicc_session'), $params); 102 103 return $contextlist; 104 } 105 106 /** 107 * Get the list of users who have data within a context. 108 * 109 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 110 */ 111 public static function get_users_in_context(userlist $userlist) { 112 $context = $userlist->get_context(); 113 114 if (!is_a($context, \context_module::class)) { 115 return; 116 } 117 118 $sql = "SELECT ss.userid 119 FROM {%s} ss 120 JOIN {modules} m 121 ON m.name = 'scorm' 122 JOIN {course_modules} cm 123 ON cm.instance = ss.scormid 124 AND cm.module = m.id 125 JOIN {context} ctx 126 ON ctx.instanceid = cm.id 127 AND ctx.contextlevel = :modlevel 128 WHERE ctx.id = :contextid"; 129 130 $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id]; 131 132 $userlist->add_from_sql('userid', sprintf($sql, 'scorm_attempt'), $params); 133 $userlist->add_from_sql('userid', sprintf($sql, 'scorm_aicc_session'), $params); 134 } 135 136 /** 137 * Export all user data for the specified user, in the specified contexts. 138 * 139 * @param approved_contextlist $contextlist The approved contexts to export information for. 140 */ 141 public static function export_user_data(approved_contextlist $contextlist) { 142 global $DB; 143 144 // Remove contexts different from COURSE_MODULE. 145 $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) { 146 if ($context->contextlevel == CONTEXT_MODULE) { 147 $carry[] = $context->id; 148 } 149 return $carry; 150 }, []); 151 152 if (empty($contexts)) { 153 return; 154 } 155 156 $user = $contextlist->get_user(); 157 $userid = $user->id; 158 // Get SCORM data. 159 foreach ($contexts as $contextid) { 160 $context = \context::instance_by_id($contextid); 161 $data = helper::get_context_data($context, $user); 162 writer::with_context($context)->export_data([], $data); 163 helper::export_context_files($context, $user); 164 } 165 166 // Get scoes_track data. 167 list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED); 168 $sql = "SELECT v.id, 169 a.attempt, 170 e.element, 171 v.value, 172 v.timemodified, 173 ctx.id as contextid 174 FROM {scorm_attempt} a 175 JOIN {scorm_scoes_value} v ON a.id = v.attemptid 176 JOIN {scorm_element} e on e.id = v.elementid 177 JOIN {course_modules} cm 178 ON cm.instance = a.scormid 179 JOIN {context} ctx 180 ON ctx.instanceid = cm.id 181 WHERE ctx.id $insql 182 AND a.userid = :userid"; 183 $params = array_merge($inparams, ['userid' => $userid]); 184 185 $alldata = []; 186 $scoestracks = $DB->get_recordset_sql($sql, $params); 187 foreach ($scoestracks as $track) { 188 $alldata[$track->contextid][$track->attempt][] = (object)[ 189 'element' => $track->element, 190 'value' => $track->value, 191 'timemodified' => transform::datetime($track->timemodified), 192 ]; 193 } 194 $scoestracks->close(); 195 196 // The scoes_track data is organised in: {Course name}/{SCORM activity name}/{My attempts}/{Attempt X}/data.json 197 // where X is the attempt number. 198 array_walk($alldata, function($attemptsdata, $contextid) { 199 $context = \context::instance_by_id($contextid); 200 array_walk($attemptsdata, function($data, $attempt) use ($context) { 201 $subcontext = [ 202 get_string('myattempts', 'scorm'), 203 get_string('attempt', 'scorm'). " $attempt" 204 ]; 205 writer::with_context($context)->export_data( 206 $subcontext, 207 (object)['scoestrack' => $data] 208 ); 209 }); 210 }); 211 212 // Get aicc_session data. 213 $sql = "SELECT ss.id, 214 ss.scormmode, 215 ss.scormstatus, 216 ss.attempt, 217 ss.lessonstatus, 218 ss.sessiontime, 219 ss.timecreated, 220 ss.timemodified, 221 ctx.id as contextid 222 FROM {scorm_aicc_session} ss 223 JOIN {course_modules} cm 224 ON cm.instance = ss.scormid 225 JOIN {context} ctx 226 ON ctx.instanceid = cm.id 227 WHERE ctx.id $insql 228 AND ss.userid = :userid"; 229 $params = array_merge($inparams, ['userid' => $userid]); 230 231 $alldata = []; 232 $aiccsessions = $DB->get_recordset_sql($sql, $params); 233 foreach ($aiccsessions as $aiccsession) { 234 $alldata[$aiccsession->contextid][] = (object)[ 235 'scormmode' => $aiccsession->scormmode, 236 'scormstatus' => $aiccsession->scormstatus, 237 'lessonstatus' => $aiccsession->lessonstatus, 238 'attempt' => $aiccsession->attempt, 239 'sessiontime' => $aiccsession->sessiontime, 240 'timecreated' => transform::datetime($aiccsession->timecreated), 241 'timemodified' => transform::datetime($aiccsession->timemodified), 242 ]; 243 } 244 $aiccsessions->close(); 245 246 // The aicc_session data is organised in: {Course name}/{SCORM activity name}/{My AICC sessions}/data.json 247 // In this case, the attempt hasn't been included in the json file because it can be null. 248 array_walk($alldata, function($data, $contextid) { 249 $context = \context::instance_by_id($contextid); 250 $subcontext = [ 251 get_string('myaiccsessions', 'scorm') 252 ]; 253 writer::with_context($context)->export_data( 254 $subcontext, 255 (object)['sessions' => $data] 256 ); 257 }); 258 } 259 260 /** 261 * Delete all user data which matches the specified context. 262 * 263 * @param context $context A user context. 264 */ 265 public static function delete_data_for_all_users_in_context(\context $context) { 266 // This should not happen, but just in case. 267 if ($context->contextlevel != CONTEXT_MODULE) { 268 return; 269 } 270 271 // Prepare SQL to gather all IDs to delete. 272 $sql = "SELECT ss.id 273 FROM {%s} ss 274 JOIN {modules} m 275 ON m.name = 'scorm' 276 JOIN {course_modules} cm 277 ON cm.instance = ss.scormid 278 AND cm.module = m.id 279 WHERE cm.id = :cmid"; 280 $params = ['cmid' => $context->instanceid]; 281 282 static::delete_data('scorm_aicc_session', $sql, $params); 283 $coursemodule = get_coursemodule_from_id('scorm', $context->instanceid); 284 scorm_delete_tracks($coursemodule->instance); 285 } 286 287 /** 288 * Delete all user data for the specified user, in the specified contexts. 289 * 290 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 291 */ 292 public static function delete_data_for_user(approved_contextlist $contextlist) { 293 global $DB; 294 295 // Remove contexts different from COURSE_MODULE. 296 $contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) { 297 if ($context->contextlevel == CONTEXT_MODULE) { 298 $carry[] = $context->id; 299 } 300 return $carry; 301 }, []); 302 303 if (empty($contextids)) { 304 return; 305 } 306 $userid = $contextlist->get_user()->id; 307 // Prepare SQL to gather all completed IDs. 308 list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED); 309 $sql = "SELECT ss.id 310 FROM {%s} ss 311 JOIN {modules} m 312 ON m.name = 'scorm' 313 JOIN {course_modules} cm 314 ON cm.instance = ss.scormid 315 AND cm.module = m.id 316 JOIN {context} ctx 317 ON ctx.instanceid = cm.id 318 WHERE ss.userid = :userid 319 AND ctx.id $insql"; 320 $params = array_merge($inparams, ['userid' => $userid]); 321 322 static::delete_data('scorm_aicc_session', $sql, $params); 323 foreach ($contextlist->get_contexts() as $context) { 324 if ($context->contextlevel == CONTEXT_MODULE) { 325 $coursemodule = get_coursemodule_from_id('scorm', $context->instanceid); 326 scorm_delete_tracks($coursemodule->instance, null, $userid); 327 } 328 } 329 } 330 331 /** 332 * Delete multiple users within a single context. 333 * 334 * @param approved_userlist $userlist The approved context and user information to delete information for. 335 */ 336 public static function delete_data_for_users(approved_userlist $userlist) { 337 global $DB; 338 $context = $userlist->get_context(); 339 340 if (!is_a($context, \context_module::class)) { 341 return; 342 } 343 344 // Prepare SQL to gather all completed IDs. 345 $userids = $userlist->get_userids(); 346 list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 347 348 $sql = "SELECT ss.id 349 FROM {%s} ss 350 JOIN {modules} m 351 ON m.name = 'scorm' 352 JOIN {course_modules} cm 353 ON cm.instance = ss.scormid 354 AND cm.module = m.id 355 JOIN {context} ctx 356 ON ctx.instanceid = cm.id 357 WHERE ctx.id = :contextid 358 AND ss.userid $insql"; 359 $params = array_merge($inparams, ['contextid' => $context->id]); 360 361 static::delete_data('scorm_aicc_session', $sql, $params); 362 $coursemodule = get_coursemodule_from_id('scorm', $context->instanceid); 363 foreach ($userlist->get_userids() as $userid) { 364 scorm_delete_tracks($coursemodule->instance, null, $userid); 365 } 366 } 367 368 /** 369 * Delete data from $tablename with the IDs returned by $sql query. 370 * 371 * @param string $tablename Table name where executing the SQL query. 372 * @param string $sql SQL query for getting the IDs of the scoestrack entries to delete. 373 * @param array $params SQL params for the query. 374 */ 375 protected static function delete_data(string $tablename, string $sql, array $params) { 376 global $DB; 377 378 $scoestracksids = $DB->get_fieldset_sql(sprintf($sql, $tablename), $params); 379 if (!empty($scoestracksids)) { 380 list($insql, $inparams) = $DB->get_in_or_equal($scoestracksids, SQL_PARAMS_NAMED); 381 $DB->delete_records_select($tablename, "id $insql", $inparams); 382 } 383 } 384 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body