Differences Between: [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_scoes_track', [ 57 'userid' => 'privacy:metadata:userid', 58 'attempt' => 'privacy:metadata:attempt', 59 'element' => 'privacy:metadata:scoes_track:element', 60 'value' => 'privacy:metadata:scoes_track:value', 61 'timemodified' => 'privacy:metadata:timemodified' 62 ], 'privacy:metadata:scorm_scoes_track'); 63 64 $collection->add_database_table('scorm_aicc_session', [ 65 'userid' => 'privacy:metadata:userid', 66 'scormmode' => 'privacy:metadata:aicc_session:scormmode', 67 'scormstatus' => 'privacy:metadata:aicc_session:scormstatus', 68 'attempt' => 'privacy:metadata:attempt', 69 'lessonstatus' => 'privacy:metadata:aicc_session:lessonstatus', 70 'sessiontime' => 'privacy:metadata:aicc_session:sessiontime', 71 'timecreated' => 'privacy:metadata:aicc_session:timecreated', 72 'timemodified' => 'privacy:metadata:timemodified', 73 ], 'privacy:metadata:scorm_aicc_session'); 74 75 $collection->add_external_location_link('aicc', [ 76 'data' => 'privacy:metadata:aicc:data' 77 ], 'privacy:metadata:aicc:externalpurpose'); 78 79 return $collection; 80 } 81 82 /** 83 * Get the list of contexts that contain user information for the specified user. 84 * 85 * @param int $userid The user to search. 86 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 87 */ 88 public static function get_contexts_for_userid(int $userid) : contextlist { 89 $sql = "SELECT ctx.id 90 FROM {%s} ss 91 JOIN {modules} m 92 ON m.name = 'scorm' 93 JOIN {course_modules} cm 94 ON cm.instance = ss.scormid 95 AND cm.module = m.id 96 JOIN {context} ctx 97 ON ctx.instanceid = cm.id 98 AND ctx.contextlevel = :modlevel 99 WHERE ss.userid = :userid"; 100 101 $params = ['modlevel' => CONTEXT_MODULE, 'userid' => $userid]; 102 $contextlist = new contextlist(); 103 $contextlist->add_from_sql(sprintf($sql, 'scorm_scoes_track'), $params); 104 $contextlist->add_from_sql(sprintf($sql, 'scorm_aicc_session'), $params); 105 106 return $contextlist; 107 } 108 109 /** 110 * Get the list of users who have data within a context. 111 * 112 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 113 */ 114 public static function get_users_in_context(userlist $userlist) { 115 $context = $userlist->get_context(); 116 117 if (!is_a($context, \context_module::class)) { 118 return; 119 } 120 121 $sql = "SELECT ss.userid 122 FROM {%s} ss 123 JOIN {modules} m 124 ON m.name = 'scorm' 125 JOIN {course_modules} cm 126 ON cm.instance = ss.scormid 127 AND cm.module = m.id 128 JOIN {context} ctx 129 ON ctx.instanceid = cm.id 130 AND ctx.contextlevel = :modlevel 131 WHERE ctx.id = :contextid"; 132 133 $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id]; 134 135 $userlist->add_from_sql('userid', sprintf($sql, 'scorm_scoes_track'), $params); 136 $userlist->add_from_sql('userid', sprintf($sql, 'scorm_aicc_session'), $params); 137 } 138 139 /** 140 * Export all user data for the specified user, in the specified contexts. 141 * 142 * @param approved_contextlist $contextlist The approved contexts to export information for. 143 */ 144 public static function export_user_data(approved_contextlist $contextlist) { 145 global $DB; 146 147 // Remove contexts different from COURSE_MODULE. 148 $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) { 149 if ($context->contextlevel == CONTEXT_MODULE) { 150 $carry[] = $context->id; 151 } 152 return $carry; 153 }, []); 154 155 if (empty($contexts)) { 156 return; 157 } 158 159 $user = $contextlist->get_user(); 160 $userid = $user->id; 161 // Get SCORM data. 162 foreach ($contexts as $contextid) { 163 $context = \context::instance_by_id($contextid); 164 $data = helper::get_context_data($context, $user); 165 writer::with_context($context)->export_data([], $data); 166 helper::export_context_files($context, $user); 167 } 168 169 // Get scoes_track data. 170 list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED); 171 $sql = "SELECT ss.id, 172 ss.attempt, 173 ss.element, 174 ss.value, 175 ss.timemodified, 176 ctx.id as contextid 177 FROM {scorm_scoes_track} ss 178 JOIN {course_modules} cm 179 ON cm.instance = ss.scormid 180 JOIN {context} ctx 181 ON ctx.instanceid = cm.id 182 WHERE ctx.id $insql 183 AND ss.userid = :userid"; 184 $params = array_merge($inparams, ['userid' => $userid]); 185 186 $alldata = []; 187 $scoestracks = $DB->get_recordset_sql($sql, $params); 188 foreach ($scoestracks as $track) { 189 $alldata[$track->contextid][$track->attempt][] = (object)[ 190 'element' => $track->element, 191 'value' => $track->value, 192 'timemodified' => transform::datetime($track->timemodified), 193 ]; 194 } 195 $scoestracks->close(); 196 197 // The scoes_track data is organised in: {Course name}/{SCORM activity name}/{My attempts}/{Attempt X}/data.json 198 // where X is the attempt number. 199 array_walk($alldata, function($attemptsdata, $contextid) { 200 $context = \context::instance_by_id($contextid); 201 array_walk($attemptsdata, function($data, $attempt) use ($context) { 202 $subcontext = [ 203 get_string('myattempts', 'scorm'), 204 get_string('attempt', 'scorm'). " $attempt" 205 ]; 206 writer::with_context($context)->export_data( 207 $subcontext, 208 (object)['scoestrack' => $data] 209 ); 210 }); 211 }); 212 213 // Get aicc_session data. 214 $sql = "SELECT ss.id, 215 ss.scormmode, 216 ss.scormstatus, 217 ss.attempt, 218 ss.lessonstatus, 219 ss.sessiontime, 220 ss.timecreated, 221 ss.timemodified, 222 ctx.id as contextid 223 FROM {scorm_aicc_session} ss 224 JOIN {course_modules} cm 225 ON cm.instance = ss.scormid 226 JOIN {context} ctx 227 ON ctx.instanceid = cm.id 228 WHERE ctx.id $insql 229 AND ss.userid = :userid"; 230 $params = array_merge($inparams, ['userid' => $userid]); 231 232 $alldata = []; 233 $aiccsessions = $DB->get_recordset_sql($sql, $params); 234 foreach ($aiccsessions as $aiccsession) { 235 $alldata[$aiccsession->contextid][] = (object)[ 236 'scormmode' => $aiccsession->scormmode, 237 'scormstatus' => $aiccsession->scormstatus, 238 'lessonstatus' => $aiccsession->lessonstatus, 239 'attempt' => $aiccsession->attempt, 240 'sessiontime' => $aiccsession->sessiontime, 241 'timecreated' => transform::datetime($aiccsession->timecreated), 242 'timemodified' => transform::datetime($aiccsession->timemodified), 243 ]; 244 } 245 $aiccsessions->close(); 246 247 // The aicc_session data is organised in: {Course name}/{SCORM activity name}/{My AICC sessions}/data.json 248 // In this case, the attempt hasn't been included in the json file because it can be null. 249 array_walk($alldata, function($data, $contextid) { 250 $context = \context::instance_by_id($contextid); 251 $subcontext = [ 252 get_string('myaiccsessions', 'scorm') 253 ]; 254 writer::with_context($context)->export_data( 255 $subcontext, 256 (object)['sessions' => $data] 257 ); 258 }); 259 } 260 261 /** 262 * Delete all user data which matches the specified context. 263 * 264 * @param context $context A user context. 265 */ 266 public static function delete_data_for_all_users_in_context(\context $context) { 267 // This should not happen, but just in case. 268 if ($context->contextlevel != CONTEXT_MODULE) { 269 return; 270 } 271 272 // Prepare SQL to gather all IDs to delete. 273 $sql = "SELECT ss.id 274 FROM {%s} ss 275 JOIN {modules} m 276 ON m.name = 'scorm' 277 JOIN {course_modules} cm 278 ON cm.instance = ss.scormid 279 AND cm.module = m.id 280 WHERE cm.id = :cmid"; 281 $params = ['cmid' => $context->instanceid]; 282 283 static::delete_data('scorm_scoes_track', $sql, $params); 284 static::delete_data('scorm_aicc_session', $sql, $params); 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_scoes_track', $sql, $params); 323 static::delete_data('scorm_aicc_session', $sql, $params); 324 } 325 326 /** 327 * Delete multiple users within a single context. 328 * 329 * @param approved_userlist $userlist The approved context and user information to delete information for. 330 */ 331 public static function delete_data_for_users(approved_userlist $userlist) { 332 global $DB; 333 $context = $userlist->get_context(); 334 335 if (!is_a($context, \context_module::class)) { 336 return; 337 } 338 339 // Prepare SQL to gather all completed IDs. 340 $userids = $userlist->get_userids(); 341 list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 342 343 $sql = "SELECT ss.id 344 FROM {%s} ss 345 JOIN {modules} m 346 ON m.name = 'scorm' 347 JOIN {course_modules} cm 348 ON cm.instance = ss.scormid 349 AND cm.module = m.id 350 JOIN {context} ctx 351 ON ctx.instanceid = cm.id 352 WHERE ctx.id = :contextid 353 AND ss.userid $insql"; 354 $params = array_merge($inparams, ['contextid' => $context->id]); 355 356 static::delete_data('scorm_scoes_track', $sql, $params); 357 static::delete_data('scorm_aicc_session', $sql, $params); 358 } 359 360 /** 361 * Delete data from $tablename with the IDs returned by $sql query. 362 * 363 * @param string $tablename Table name where executing the SQL query. 364 * @param string $sql SQL query for getting the IDs of the scoestrack entries to delete. 365 * @param array $params SQL params for the query. 366 */ 367 protected static function delete_data(string $tablename, string $sql, array $params) { 368 global $DB; 369 370 $scoestracksids = $DB->get_fieldset_sql(sprintf($sql, $tablename), $params); 371 if (!empty($scoestracksids)) { 372 list($insql, $inparams) = $DB->get_in_or_equal($scoestracksids, SQL_PARAMS_NAMED); 373 $DB->delete_records_select($tablename, "id $insql", $inparams); 374 } 375 } 376 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body