See Release Notes
Long Term Support Release
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 * Data provider. 19 * 20 * @package core_badges 21 * @copyright 2018 Frédéric Massart 22 * @author Frédéric Massart <fred@branchup.tech> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace core_badges\privacy; 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 use badge; 31 use context; 32 use context_course; 33 use context_helper; 34 use context_system; 35 use context_user; 36 use core_text; 37 use core_privacy\local\metadata\collection; 38 use core_privacy\local\request\approved_contextlist; 39 use core_privacy\local\request\transform; 40 use core_privacy\local\request\writer; 41 use core_privacy\local\request\userlist; 42 use core_privacy\local\request\approved_userlist; 43 44 require_once($CFG->libdir . '/badgeslib.php'); 45 46 /** 47 * Data provider class. 48 * 49 * @package core_badges 50 * @copyright 2018 Frédéric Massart 51 * @author Frédéric Massart <fred@branchup.tech> 52 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 53 */ 54 class provider implements 55 \core_privacy\local\metadata\provider, 56 \core_privacy\local\request\core_userlist_provider, 57 \core_privacy\local\request\subsystem\provider { 58 59 /** 60 * Returns metadata. 61 * 62 * @param collection $collection The initialised collection to add items to. 63 * @return collection A listing of user data stored through this system. 64 */ 65 public static function get_metadata(collection $collection) : collection { 66 67 $collection->add_database_table('badge', [ 68 'usercreated' => 'privacy:metadata:badge:usercreated', 69 'usermodified' => 'privacy:metadata:badge:usermodified', 70 'timecreated' => 'privacy:metadata:badge:timecreated', 71 'timemodified' => 'privacy:metadata:badge:timemodified', 72 ], 'privacy:metadata:badge'); 73 74 $collection->add_database_table('badge_issued', [ 75 'userid' => 'privacy:metadata:issued:userid', 76 'dateissued' => 'privacy:metadata:issued:dateissued', 77 'dateexpire' => 'privacy:metadata:issued:dateexpire', 78 ], 'privacy:metadata:issued'); 79 80 $collection->add_database_table('badge_criteria_met', [ 81 'userid' => 'privacy:metadata:criteriamet:userid', 82 'datemet' => 'privacy:metadata:criteriamet:datemet', 83 ], 'privacy:metadata:criteriamet'); 84 85 $collection->add_database_table('badge_manual_award', [ 86 'recipientid' => 'privacy:metadata:manualaward:recipientid', 87 'issuerid' => 'privacy:metadata:manualaward:issuerid', 88 'issuerrole' => 'privacy:metadata:manualaward:issuerrole', 89 'datemet' => 'privacy:metadata:manualaward:datemet', 90 ], 'privacy:metadata:manualaward'); 91 92 $collection->add_database_table('badge_backpack', [ 93 'userid' => 'privacy:metadata:backpack:userid', 94 'email' => 'privacy:metadata:backpack:email', 95 'externalbackpackid' => 'privacy:metadata:backpack:externalbackpackid', 96 'backpackuid' => 'privacy:metadata:backpack:backpackuid', 97 // The columns autosync and password are not used. 98 ], 'privacy:metadata:backpack'); 99 100 $collection->add_external_location_link('backpacks', [ 101 'name' => 'privacy:metadata:external:backpacks:badge', 102 'description' => 'privacy:metadata:external:backpacks:description', 103 'image' => 'privacy:metadata:external:backpacks:image', 104 'url' => 'privacy:metadata:external:backpacks:url', 105 'issuer' => 'privacy:metadata:external:backpacks:issuer', 106 ], 'privacy:metadata:external:backpacks'); 107 108 $collection->add_database_table('badge_backpack_oauth2', [ 109 'userid' => 'privacy:metadata:backpackoauth2:userid', 110 'usermodified' => 'privacy:metadata:backpackoauth2:usermodified', 111 'token' => 'privacy:metadata:backpackoauth2:token', 112 'issuerid' => 'privacy:metadata:backpackoauth2:issuerid', 113 'scope' => 'privacy:metadata:backpackoauth2:scope', 114 ], 'privacy:metadata:backpackoauth2'); 115 116 return $collection; 117 } 118 119 /** 120 * Get the list of contexts that contain user information for the specified user. 121 * 122 * @param int $userid The user to search. 123 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 124 */ 125 public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { 126 $contextlist = new \core_privacy\local\request\contextlist(); 127 128 // Find the modifications we made on badges (course & system). 129 $sql = " 130 SELECT ctx.id 131 FROM {badge} b 132 JOIN {context} ctx 133 ON (b.type = :typecourse AND b.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel) 134 OR (b.type = :typesite AND ctx.id = :syscontextid) 135 WHERE b.usermodified = :userid1 136 OR b.usercreated = :userid2"; 137 $params = [ 138 'courselevel' => CONTEXT_COURSE, 139 'syscontextid' => SYSCONTEXTID, 140 'typecourse' => BADGE_TYPE_COURSE, 141 'typesite' => BADGE_TYPE_SITE, 142 'userid1' => $userid, 143 'userid2' => $userid, 144 ]; 145 $contextlist->add_from_sql($sql, $params); 146 147 // Find where we've manually awarded a badge (recipient user context). 148 $sql = " 149 SELECT ctx.id 150 FROM {badge_manual_award} bma 151 JOIN {context} ctx 152 ON ctx.instanceid = bma.recipientid 153 AND ctx.contextlevel = :userlevel 154 WHERE bma.issuerid = :userid"; 155 $params = [ 156 'userlevel' => CONTEXT_USER, 157 'userid' => $userid, 158 ]; 159 $contextlist->add_from_sql($sql, $params); 160 161 // Now find where there is real user data (user context). 162 $sql = " 163 SELECT ctx.id 164 FROM {context} ctx 165 LEFT JOIN {badge_manual_award} bma 166 ON bma.recipientid = ctx.instanceid 167 LEFT JOIN {badge_issued} bi 168 ON bi.userid = ctx.instanceid 169 LEFT JOIN {badge_criteria_met} bcm 170 ON bcm.userid = ctx.instanceid 171 LEFT JOIN {badge_backpack} bb 172 ON bb.userid = ctx.instanceid 173 WHERE ctx.contextlevel = :userlevel 174 AND ctx.instanceid = :userid 175 AND (bma.id IS NOT NULL 176 OR bi.id IS NOT NULL 177 OR bcm.id IS NOT NULL 178 OR bb.id IS NOT NULL)"; 179 $params = [ 180 'userlevel' => CONTEXT_USER, 181 'userid' => $userid, 182 ]; 183 $contextlist->add_from_sql($sql, $params); 184 185 return $contextlist; 186 } 187 188 /** 189 * Get the list of users within a specific context. 190 * 191 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 192 */ 193 public static function get_users_in_context(userlist $userlist) { 194 $context = $userlist->get_context(); 195 196 $allowedcontexts = [ 197 CONTEXT_COURSE, 198 CONTEXT_SYSTEM, 199 CONTEXT_USER 200 ]; 201 202 if (!in_array($context->contextlevel, $allowedcontexts)) { 203 return; 204 } 205 206 if ($context->contextlevel == CONTEXT_COURSE || $context->contextlevel == CONTEXT_SYSTEM) { 207 // Find the modifications we made on badges (course & system). 208 if ($context->contextlevel == CONTEXT_COURSE) { 209 $extrawhere = 'AND b.courseid = :courseid'; 210 $params = [ 211 'badgetype' => BADGE_TYPE_COURSE, 212 'courseid' => $context->instanceid 213 ]; 214 } else { 215 $extrawhere = ''; 216 $params = ['badgetype' => BADGE_TYPE_SITE]; 217 } 218 219 $sql = "SELECT b.usermodified, b.usercreated 220 FROM {badge} b 221 WHERE b.type = :badgetype 222 $extrawhere"; 223 224 $userlist->add_from_sql('usermodified', $sql, $params); 225 $userlist->add_from_sql('usercreated', $sql, $params); 226 } 227 228 if ($context->contextlevel == CONTEXT_USER) { 229 // Find where we've manually awarded a badge (recipient user context). 230 $params = [ 231 'instanceid' => $context->instanceid 232 ]; 233 234 $sql = "SELECT issuerid, recipientid 235 FROM {badge_manual_award} 236 WHERE recipientid = :instanceid"; 237 238 $userlist->add_from_sql('issuerid', $sql, $params); 239 $userlist->add_from_sql('recipientid', $sql, $params); 240 241 $sql = "SELECT userid 242 FROM {badge_issued} 243 WHERE userid = :instanceid"; 244 245 $userlist->add_from_sql('userid', $sql, $params); 246 247 $sql = "SELECT userid 248 FROM {badge_criteria_met} 249 WHERE userid = :instanceid"; 250 251 $userlist->add_from_sql('userid', $sql, $params); 252 253 $sql = "SELECT userid 254 FROM {badge_backpack} 255 WHERE userid = :instanceid"; 256 257 $userlist->add_from_sql('userid', $sql, $params); 258 } 259 } 260 261 /** 262 * Export all user data for the specified user, in the specified contexts. 263 * 264 * @param approved_contextlist $contextlist The approved contexts to export information for. 265 */ 266 public static function export_user_data(approved_contextlist $contextlist) { 267 global $DB; 268 269 $userid = $contextlist->get_user()->id; 270 $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) { 271 $level = $context->contextlevel; 272 if ($level == CONTEXT_USER || $level == CONTEXT_COURSE) { 273 $carry[$level][] = $context->instanceid; 274 } else if ($level == CONTEXT_SYSTEM) { 275 $carry[$level] = SYSCONTEXTID; 276 } 277 return $carry; 278 }, [ 279 CONTEXT_COURSE => [], 280 CONTEXT_USER => [], 281 CONTEXT_SYSTEM => null, 282 ]); 283 284 $path = [get_string('badges', 'core_badges')]; 285 $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); 286 287 // Export the badges we've created or modified. 288 if (!empty($contexts[CONTEXT_SYSTEM]) || !empty($contexts[CONTEXT_COURSE])) { 289 $sqls = []; 290 $params = []; 291 292 if (!empty($contexts[CONTEXT_SYSTEM])) { 293 $sqls[] = "b.type = :typesite"; 294 $params['typesite'] = BADGE_TYPE_SITE; 295 } 296 297 if (!empty($contexts[CONTEXT_COURSE])) { 298 list($insql, $inparams) = $DB->get_in_or_equal($contexts[CONTEXT_COURSE], SQL_PARAMS_NAMED); 299 $sqls[] = "(b.type = :typecourse AND b.courseid $insql)"; 300 $params = array_merge($params, ['typecourse' => BADGE_TYPE_COURSE], $inparams); 301 } 302 303 $sqlwhere = '(' . implode(' OR ', $sqls) . ')'; 304 $sql = " 305 SELECT b.*, COALESCE(b.courseid, 0) AS normalisedcourseid 306 FROM {badge} b 307 WHERE (b.usermodified = :userid1 OR b.usercreated = :userid2) 308 AND $sqlwhere 309 ORDER BY b.courseid, b.id"; 310 $params = array_merge($params, ['userid1' => $userid, 'userid2' => $userid]); 311 $recordset = $DB->get_recordset_sql($sql, $params); 312 static::recordset_loop_and_export($recordset, 'normalisedcourseid', [], function($carry, $record) use ($userid) { 313 $carry[] = [ 314 'name' => $record->name, 315 'created_on' => transform::datetime($record->timecreated), 316 'created_by_you' => transform::yesno($record->usercreated == $userid), 317 'modified_on' => transform::datetime($record->timemodified), 318 'modified_by_you' => transform::yesno($record->usermodified == $userid), 319 ]; 320 return $carry; 321 }, function($courseid, $data) use ($path) { 322 $context = $courseid ? context_course::instance($courseid) : context_system::instance(); 323 writer::with_context($context)->export_data($path, (object) ['badges' => $data]); 324 }); 325 } 326 327 // Export the badges we've manually awarded. 328 if (!empty($contexts[CONTEXT_USER])) { 329 list($insql, $inparams) = $DB->get_in_or_equal($contexts[CONTEXT_USER], SQL_PARAMS_NAMED); 330 $sql = " 331 SELECT bma.id, bma.recipientid, bma.datemet, b.name, b.courseid, 332 r.id AS roleid, 333 r.name AS rolename, 334 r.shortname AS roleshortname, 335 r.archetype AS rolearchetype, 336 $ctxfields 337 FROM {badge_manual_award} bma 338 JOIN {badge} b 339 ON b.id = bma.badgeid 340 JOIN {role} r 341 ON r.id = bma.issuerrole 342 JOIN {context} ctx 343 ON (COALESCE(b.courseid, 0) > 0 AND ctx.instanceid = b.courseid AND ctx.contextlevel = :courselevel) 344 OR (COALESCE(b.courseid, 0) = 0 AND ctx.id = :syscontextid) 345 WHERE bma.recipientid $insql 346 AND bma.issuerid = :userid 347 ORDER BY bma.recipientid, bma.id"; 348 $params = array_merge($inparams, [ 349 'courselevel' => CONTEXT_COURSE, 350 'syscontextid' => SYSCONTEXTID, 351 'userid' => $userid 352 ]); 353 $recordset = $DB->get_recordset_sql($sql, $params); 354 static::recordset_loop_and_export($recordset, 'recipientid', [], function($carry, $record) use ($userid) { 355 356 // The only reason we fetch the context and role is to format the name of the role, which could be 357 // different to the standard name if the badge was created in a course. 358 context_helper::preload_from_record($record); 359 $context = $record->courseid ? context_course::instance($record->courseid) : context_system::instance(); 360 $role = (object) [ 361 'id' => $record->roleid, 362 'name' => $record->rolename, 363 'shortname' => $record->roleshortname, 364 'archetype' => $record->rolearchetype, 365 // Mock those two fields as they do not matter. 366 'sortorder' => 0, 367 'description' => '' 368 ]; 369 370 $carry[] = [ 371 'name' => $record->name, 372 'issued_by_you' => transform::yesno(true), 373 'issued_on' => transform::datetime($record->datemet), 374 'issuer_role' => role_get_name($role, $context), 375 ]; 376 return $carry; 377 }, function($userid, $data) use ($path) { 378 $context = context_user::instance($userid); 379 writer::with_context($context)->export_related_data($path, 'manual_awards', (object) ['badges' => $data]); 380 }); 381 } 382 383 // Export our data. 384 if (in_array($userid, $contexts[CONTEXT_USER])) { 385 386 // Export the badges. 387 $uniqueid = $DB->sql_concat_join("'-'", ['b.id', 'COALESCE(bc.id, 0)', 'COALESCE(bi.id, 0)', 388 'COALESCE(bma.id, 0)', 'COALESCE(bcm.id, 0)', 'COALESCE(brb.id, 0)', 'COALESCE(ba.id, 0)']); 389 $sql = " 390 SELECT $uniqueid AS uniqueid, b.id, 391 bi.id AS biid, bi.dateissued, bi.dateexpire, bi.uniquehash, 392 bma.id AS bmaid, bma.datemet, bma.issuerid, 393 bcm.id AS bcmid, 394 c.fullname AS coursename, 395 be.id AS beid, 396 be.issuername AS beissuername, 397 be.issuerurl AS beissuerurl, 398 be.issueremail AS beissueremail, 399 be.claimid AS beclaimid, 400 be.claimcomment AS beclaimcomment, 401 be.dateissued AS bedateissued, 402 brb.id as rbid, 403 brb.badgeid as rbbadgeid, 404 brb.relatedbadgeid as rbrelatedbadgeid, 405 ba.id as baid, 406 ba.targetname as batargetname, 407 ba.targeturl as batargeturl, 408 ba.targetdescription as batargetdescription, 409 ba.targetframework as batargetframework, 410 ba.targetcode as batargetcode, 411 $ctxfields 412 FROM {badge} b 413 LEFT JOIN {badge_issued} bi 414 ON bi.badgeid = b.id 415 AND bi.userid = :userid1 416 LEFT JOIN {badge_related} brb 417 ON ( b.id = brb.badgeid OR b.id = brb.relatedbadgeid ) 418 LEFT JOIN {badge_alignment} ba 419 ON ( b.id = ba.badgeid ) 420 LEFT JOIN {badge_endorsement} be 421 ON be.badgeid = b.id 422 LEFT JOIN {badge_manual_award} bma 423 ON bma.badgeid = b.id 424 AND bma.recipientid = :userid2 425 LEFT JOIN {badge_criteria} bc 426 ON bc.badgeid = b.id 427 LEFT JOIN {badge_criteria_met} bcm 428 ON bcm.critid = bc.id 429 AND bcm.userid = :userid3 430 LEFT JOIN {course} c 431 ON c.id = b.courseid 432 AND b.type = :typecourse 433 LEFT JOIN {context} ctx 434 ON ctx.instanceid = c.id 435 AND ctx.contextlevel = :courselevel 436 WHERE bi.id IS NOT NULL 437 OR bma.id IS NOT NULL 438 OR bcm.id IS NOT NULL 439 ORDER BY b.id"; 440 $params = [ 441 'userid1' => $userid, 442 'userid2' => $userid, 443 'userid3' => $userid, 444 'courselevel' => CONTEXT_COURSE, 445 'typecourse' => BADGE_TYPE_COURSE, 446 ]; 447 $recordset = $DB->get_recordset_sql($sql, $params); 448 static::recordset_loop_and_export($recordset, 'id', null, function($carry, $record) use ($userid) { 449 $badge = new badge($record->id); 450 451 // Export details of the badge. 452 if ($carry === null) { 453 $carry = [ 454 'name' => $badge->name, 455 'version' => $badge->version, 456 'language' => $badge->language, 457 'imageauthorname' => $badge->imageauthorname, 458 'imageauthoremail' => $badge->imageauthoremail, 459 'imageauthorurl' => $badge->imageauthorurl, 460 'imagecaption' => $badge->imagecaption, 461 'issued' => null, 462 'manual_award' => null, 463 'criteria_met' => [], 464 'endorsement' => null, 465 ]; 466 467 if ($badge->type == BADGE_TYPE_COURSE) { 468 context_helper::preload_from_record($record); 469 $carry['course'] = format_string($record->coursename, true, ['context' => $badge->get_context()]); 470 } 471 472 if (!empty($record->beid)) { 473 $carry['endorsement'] = [ 474 'issuername' => $record->beissuername, 475 'issuerurl' => $record->beissuerurl, 476 'issueremail' => $record->beissueremail, 477 'claimid' => $record->beclaimid, 478 'claimcomment' => $record->beclaimcomment, 479 'dateissued' => $record->bedateissued ? transform::datetime($record->bedateissued) : null 480 ]; 481 } 482 483 if (!empty($record->biid)) { 484 $carry['issued'] = [ 485 'issued_on' => transform::datetime($record->dateissued), 486 'expires_on' => $record->dateexpire ? transform::datetime($record->dateexpire) : null, 487 'unique_hash' => $record->uniquehash, 488 ]; 489 } 490 491 if (!empty($record->bmaid)) { 492 $carry['manual_award'] = [ 493 'awarded_on' => transform::datetime($record->datemet), 494 'issuer' => transform::user($record->issuerid) 495 ]; 496 } 497 } 498 if (!empty($record->rbid)) { 499 if (empty($carry['related_badge'])) { 500 $carry['related_badge'] = []; 501 } 502 $rbid = $record->rbbadgeid; 503 if ($rbid == $record->id) { 504 $rbid = $record->rbrelatedbadgeid; 505 } 506 $exists = false; 507 foreach ($carry['related_badge'] as $related) { 508 if ($related['badgeid'] == $rbid) { 509 $exists = true; 510 break; 511 } 512 } 513 if (!$exists) { 514 $relatedbadge = new badge($rbid); 515 $carry['related_badge'][] = [ 516 'badgeid' => $rbid, 517 'badgename' => $relatedbadge->name 518 ]; 519 } 520 } 521 522 if (!empty($record->baid)) { 523 if (empty($carry['alignment'])) { 524 $carry['alignment'] = []; 525 } 526 $exists = false; 527 $newalignment = [ 528 'targetname' => $record->batargetname, 529 'targeturl' => $record->batargeturl, 530 'targetdescription' => $record->batargetdescription, 531 'targetframework' => $record->batargetframework, 532 'targetcode' => $record->batargetcode, 533 ]; 534 foreach ($carry['alignment'] as $alignment) { 535 if ($alignment == $newalignment) { 536 $exists = true; 537 break; 538 } 539 } 540 if (!$exists) { 541 $carry['alignment'][] = $newalignment; 542 } 543 } 544 545 // Export the details of the criteria met. 546 // We only do that once, when we find that a least one criteria was met. 547 // This is heavily based on the logic present in core_badges_renderer::render_issued_badge. 548 if (!empty($record->bcmid) && empty($carry['criteria_met'])) { 549 550 $agg = $badge->get_aggregation_methods(); 551 $evidenceids = array_map(function($record) { 552 return $record->critid; 553 }, $badge->get_criteria_completions($userid)); 554 555 $criteria = $badge->criteria; 556 unset($criteria[BADGE_CRITERIA_TYPE_OVERALL]); 557 558 $items = []; 559 foreach ($criteria as $type => $c) { 560 if (in_array($c->id, $evidenceids)) { 561 $details = $c->get_details(true); 562 if (count($c->params) == 1) { 563 $items[] = get_string('criteria_descr_single_' . $type , 'core_badges') . ' ' . $details; 564 } else { 565 $items[] = get_string('criteria_descr_' . $type , 'core_badges', 566 core_text::strtoupper($agg[$badge->get_aggregation_method($type)])) . ' ' . $details; 567 } 568 } 569 } 570 $carry['criteria_met'] = $items; 571 } 572 return $carry; 573 }, function($badgeid, $data) use ($path, $userid) { 574 $path = array_merge($path, ["{$data['name']} ({$badgeid})"]); 575 $writer = writer::with_context(context_user::instance($userid)); 576 $writer->export_data($path, (object) $data); 577 $writer->export_area_files($path, 'badges', 'userbadge', $badgeid); 578 }); 579 580 // Export the backpacks. 581 $data = []; 582 $recordset = $DB->get_recordset_select('badge_backpack', 'userid = :userid', ['userid' => $userid]); 583 foreach ($recordset as $record) { 584 $data[] = [ 585 'email' => $record->email, 586 'externalbackpackid' => $record->externalbackpackid, 587 'uid' => $record->backpackuid 588 ]; 589 } 590 $recordset->close(); 591 if (!empty($data)) { 592 writer::with_context(context_user::instance($userid))->export_related_data($path, 'backpacks', 593 (object) ['backpacks' => $data]); 594 } 595 } 596 } 597 598 /** 599 * Delete all data for all users in the specified context. 600 * 601 * @param context $context The specific context to delete data for. 602 */ 603 public static function delete_data_for_all_users_in_context(context $context) { 604 // We cannot delete the course or system data as it is needed by the system. 605 if ($context->contextlevel != CONTEXT_USER) { 606 return; 607 } 608 609 // Delete all the user data. 610 static::delete_user_data($context->instanceid); 611 } 612 613 /** 614 * Delete multiple users within a single context. 615 * 616 * @param approved_userlist $userlist The approved context and user information to delete information for. 617 */ 618 public static function delete_data_for_users(approved_userlist $userlist) { 619 $context = $userlist->get_context(); 620 621 if (!in_array($context->instanceid, $userlist->get_userids())) { 622 return; 623 } 624 625 if ($context->contextlevel == CONTEXT_USER) { 626 // We can only delete our own data in the user context, nothing in course or system. 627 static::delete_user_data($context->instanceid); 628 } 629 } 630 631 /** 632 * Delete all user data for the specified user, in the specified contexts. 633 * 634 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 635 */ 636 public static function delete_data_for_user(approved_contextlist $contextlist) { 637 $userid = $contextlist->get_user()->id; 638 foreach ($contextlist->get_contexts() as $context) { 639 if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) { 640 // We can only delete our own data in the user context, nothing in course or system. 641 static::delete_user_data($userid); 642 break; 643 } 644 } 645 } 646 647 /** 648 * Delete all the data for a user. 649 * 650 * @param int $userid The user ID. 651 * @return void 652 */ 653 protected static function delete_user_data($userid) { 654 global $DB; 655 656 // Delete the stuff. 657 $DB->delete_records('badge_manual_award', ['recipientid' => $userid]); 658 $DB->delete_records('badge_criteria_met', ['userid' => $userid]); 659 $DB->delete_records('badge_issued', ['userid' => $userid]); 660 661 // Delete the backpacks and related stuff. 662 $backpackids = $DB->get_fieldset_select('badge_backpack', 'id', 'userid = :userid', ['userid' => $userid]); 663 if (!empty($backpackids)) { 664 list($insql, $inparams) = $DB->get_in_or_equal($backpackids, SQL_PARAMS_NAMED); 665 $DB->delete_records_select('badge_external', "backpackid $insql", $inparams); 666 $DB->delete_records_select('badge_backpack', "id $insql", $inparams); 667 } 668 } 669 670 /** 671 * Loop and export from a recordset. 672 * 673 * @param \moodle_recordset $recordset The recordset. 674 * @param string $splitkey The record key to determine when to export. 675 * @param mixed $initial The initial data to reduce from. 676 * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. 677 * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. 678 * @return void 679 */ 680 protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial, 681 callable $reducer, callable $export) { 682 683 $data = $initial; 684 $lastid = null; 685 686 foreach ($recordset as $record) { 687 if ($lastid !== null && $record->{$splitkey} != $lastid) { 688 $export($lastid, $data); 689 $data = $initial; 690 } 691 $data = $reducer($data, $record); 692 $lastid = $record->{$splitkey}; 693 } 694 $recordset->close(); 695 696 if ($lastid !== null) { 697 $export($lastid, $data); 698 } 699 } 700 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body