Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}