Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
                        'imagecaption' => $badge->imagecaption,
458
                        'issued' => null,
459
                        'manual_award' => null,
460
                        'criteria_met' => [],
461
                        'endorsement' => null,
462
                    ];
463
 
464
                    if ($badge->type == BADGE_TYPE_COURSE) {
465
                        context_helper::preload_from_record($record);
466
                        $carry['course'] = format_string($record->coursename, true, ['context' => $badge->get_context()]);
467
                    }
468
 
469
                    if (!empty($record->beid)) {
470
                        $carry['endorsement'] = [
471
                            'issuername' => $record->beissuername,
472
                            'issuerurl' => $record->beissuerurl,
473
                            'issueremail' => $record->beissueremail,
474
                            'claimid' => $record->beclaimid,
475
                            'claimcomment' => $record->beclaimcomment,
476
                            'dateissued' => $record->bedateissued ? transform::datetime($record->bedateissued) : null
477
                        ];
478
                    }
479
 
480
                    if (!empty($record->biid)) {
481
                        $carry['issued'] = [
482
                            'issued_on' => transform::datetime($record->dateissued),
483
                            'expires_on' => $record->dateexpire ? transform::datetime($record->dateexpire) : null,
484
                            'unique_hash' => $record->uniquehash,
485
                        ];
486
                    }
487
 
488
                    if (!empty($record->bmaid)) {
489
                        $carry['manual_award'] = [
490
                            'awarded_on' => transform::datetime($record->datemet),
491
                            'issuer' => transform::user($record->issuerid)
492
                        ];
493
                    }
494
                }
495
                if (!empty($record->rbid)) {
496
                    if (empty($carry['related_badge'])) {
497
                        $carry['related_badge'] = [];
498
                    }
499
                    $rbid = $record->rbbadgeid;
500
                    if ($rbid == $record->id) {
501
                        $rbid = $record->rbrelatedbadgeid;
502
                    }
503
                    $exists = false;
504
                    foreach ($carry['related_badge'] as $related) {
505
                        if ($related['badgeid'] == $rbid) {
506
                            $exists = true;
507
                            break;
508
                        }
509
                    }
510
                    if (!$exists) {
511
                        $relatedbadge = new badge($rbid);
512
                        $carry['related_badge'][] = [
513
                            'badgeid' => $rbid,
514
                            'badgename' => $relatedbadge->name
515
                        ];
516
                    }
517
                }
518
 
519
                if (!empty($record->baid)) {
520
                    if (empty($carry['alignment'])) {
521
                        $carry['alignment'] = [];
522
                    }
523
                    $exists = false;
524
                    $newalignment = [
525
                        'targetname' => $record->batargetname,
526
                        'targeturl' => $record->batargeturl,
527
                        'targetdescription' => $record->batargetdescription,
528
                        'targetframework' => $record->batargetframework,
529
                        'targetcode' => $record->batargetcode,
530
                    ];
531
                    foreach ($carry['alignment'] as $alignment) {
532
                        if ($alignment == $newalignment) {
533
                            $exists = true;
534
                            break;
535
                        }
536
                    }
537
                    if (!$exists) {
538
                        $carry['alignment'][] = $newalignment;
539
                    }
540
                }
541
 
542
                // Export the details of the criteria met.
543
                // We only do that once, when we find that a least one criteria was met.
544
                // This is heavily based on the logic present in core_badges_renderer::render_issued_badge.
545
                if (!empty($record->bcmid) && empty($carry['criteria_met'])) {
546
 
547
                    $agg = $badge->get_aggregation_methods();
548
                    $evidenceids = array_map(function($record) {
549
                        return $record->critid;
550
                    }, $badge->get_criteria_completions($userid));
551
 
552
                    $criteria = $badge->criteria;
553
                    unset($criteria[BADGE_CRITERIA_TYPE_OVERALL]);
554
 
555
                    $items = [];
556
                    foreach ($criteria as $type => $c) {
557
                        if (in_array($c->id, $evidenceids)) {
558
                            $details = $c->get_details(true);
559
                            if (count($c->params) == 1) {
560
                                $items[] = get_string('criteria_descr_single_' . $type , 'core_badges') . ' ' . $details;
561
                            } else {
562
                                $items[] = get_string('criteria_descr_' . $type , 'core_badges',
563
                                    core_text::strtoupper($agg[$badge->get_aggregation_method($type)])) . ' ' . $details;
564
                            }
565
                        }
566
                    }
567
                    $carry['criteria_met'] = $items;
568
                }
569
                return $carry;
570
            }, function($badgeid, $data) use ($path, $userid) {
571
                $path = array_merge($path, ["{$data['name']} ({$badgeid})"]);
572
                $writer = writer::with_context(context_user::instance($userid));
573
                $writer->export_data($path, (object) $data);
574
                $writer->export_area_files($path, 'badges', 'userbadge', $badgeid);
575
            });
576
 
577
            // Export the backpacks.
578
            $data = [];
579
            $recordset = $DB->get_recordset_select('badge_backpack', 'userid = :userid', ['userid' => $userid]);
580
            foreach ($recordset as $record) {
581
                $data[] = [
582
                    'email' => $record->email,
583
                    'externalbackpackid' => $record->externalbackpackid,
584
                    'uid' => $record->backpackuid
585
                ];
586
            }
587
            $recordset->close();
588
            if (!empty($data)) {
589
                writer::with_context(context_user::instance($userid))->export_related_data($path, 'backpacks',
590
                    (object) ['backpacks' => $data]);
591
            }
592
        }
593
    }
594
 
595
    /**
596
     * Delete all data for all users in the specified context.
597
     *
598
     * @param context $context The specific context to delete data for.
599
     */
600
    public static function delete_data_for_all_users_in_context(context $context) {
601
        // We cannot delete the course or system data as it is needed by the system.
602
        if ($context->contextlevel != CONTEXT_USER) {
603
            return;
604
        }
605
 
606
        // Delete all the user data.
607
        static::delete_user_data($context->instanceid);
608
    }
609
 
610
    /**
611
     * Delete multiple users within a single context.
612
     *
613
     * @param approved_userlist $userlist The approved context and user information to delete information for.
614
     */
615
    public static function delete_data_for_users(approved_userlist $userlist) {
616
        $context = $userlist->get_context();
617
 
618
        if (!in_array($context->instanceid, $userlist->get_userids())) {
619
            return;
620
        }
621
 
622
        if ($context->contextlevel == CONTEXT_USER) {
623
            // We can only delete our own data in the user context, nothing in course or system.
624
            static::delete_user_data($context->instanceid);
625
        }
626
    }
627
 
628
    /**
629
     * Delete all user data for the specified user, in the specified contexts.
630
     *
631
     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
632
     */
633
    public static function delete_data_for_user(approved_contextlist $contextlist) {
634
        $userid = $contextlist->get_user()->id;
635
        foreach ($contextlist->get_contexts() as $context) {
636
            if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) {
637
                // We can only delete our own data in the user context, nothing in course or system.
638
                static::delete_user_data($userid);
639
                break;
640
            }
641
        }
642
    }
643
 
644
    /**
645
     * Delete all the data for a user.
646
     *
647
     * @param int $userid The user ID.
648
     * @return void
649
     */
650
    protected static function delete_user_data($userid) {
651
        global $DB;
652
 
653
        // Delete the stuff.
654
        $DB->delete_records('badge_manual_award', ['recipientid' => $userid]);
655
        $DB->delete_records('badge_criteria_met', ['userid' => $userid]);
656
        $DB->delete_records('badge_issued', ['userid' => $userid]);
657
 
658
        // Delete the backpacks and related stuff.
659
        $backpackids = $DB->get_fieldset_select('badge_backpack', 'id', 'userid = :userid', ['userid' => $userid]);
660
        if (!empty($backpackids)) {
661
            list($insql, $inparams) = $DB->get_in_or_equal($backpackids, SQL_PARAMS_NAMED);
662
            $DB->delete_records_select('badge_external', "backpackid $insql", $inparams);
663
            $DB->delete_records_select('badge_backpack', "id $insql", $inparams);
664
        }
665
    }
666
 
667
    /**
668
     * Loop and export from a recordset.
669
     *
670
     * @param \moodle_recordset $recordset The recordset.
671
     * @param string $splitkey The record key to determine when to export.
672
     * @param mixed $initial The initial data to reduce from.
673
     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
674
     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
675
     * @return void
676
     */
677
    protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
678
            callable $reducer, callable $export) {
679
 
680
        $data = $initial;
681
        $lastid = null;
682
 
683
        foreach ($recordset as $record) {
684
            if ($lastid !== null && $record->{$splitkey} != $lastid) {
685
                $export($lastid, $data);
686
                $data = $initial;
687
            }
688
            $data = $reducer($data, $record);
689
            $lastid = $record->{$splitkey};
690
        }
691
        $recordset->close();
692
 
693
        if ($lastid !== null) {
694
            $export($lastid, $data);
695
        }
696
    }
697
}