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
 * Privacy class for requesting user data.
19
 *
20
 * @package    core_user
21
 * @copyright  2018 Adrian Greeve <adrian@moodle.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace core_user\privacy;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
use \core_privacy\local\metadata\collection;
30
use \core_privacy\local\request\transform;
31
use \core_privacy\local\request\contextlist;
32
use \core_privacy\local\request\approved_contextlist;
33
use \core_privacy\local\request\writer;
34
use core_privacy\local\request\userlist;
35
use \core_privacy\local\request\approved_userlist;
36
 
37
/**
38
 * Privacy class for requesting user data.
39
 *
40
 * @package    core_comment
41
 * @copyright  2018 Adrian Greeve <adrian@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\subsystem\provider,
48
        \core_privacy\local\request\user_preference_provider {
49
 
50
    /**
51
     * Returns information about the user data stored in this component.
52
     *
53
     * @param  collection $collection A list of information about this component
54
     * @return collection The collection object filled out with information about this component.
55
     */
56
    public static function get_metadata(collection $collection): collection {
57
        $userfields = [
58
            'id' => 'privacy:metadata:id',
59
            'auth' => 'privacy:metadata:auth',
60
            'confirmed' => 'privacy:metadata:confirmed',
61
            'policyagreed' => 'privacy:metadata:policyagreed',
62
            'deleted' => 'privacy:metadata:deleted',
63
            'suspended' => 'privacy:metadata:suspended',
64
            'mnethostid' => 'privacy:metadata:mnethostid',
65
            'username' => 'privacy:metadata:username',
66
            'password' => 'privacy:metadata:password',
67
            'idnumber' => 'privacy:metadata:idnumber',
68
            'firstname' => 'privacy:metadata:firstname',
69
            'lastname' => 'privacy:metadata:lastname',
70
            'email' => 'privacy:metadata:email',
71
            'emailstop' => 'privacy:metadata:emailstop',
72
            'phone1' => 'privacy:metadata:phone',
73
            'phone2' => 'privacy:metadata:phone',
74
            'institution' => 'privacy:metadata:institution',
75
            'department' => 'privacy:metadata:department',
76
            'address' => 'privacy:metadata:address',
77
            'city' => 'privacy:metadata:city',
78
            'country' => 'privacy:metadata:country',
79
            'lang' => 'privacy:metadata:lang',
80
            'calendartype' => 'privacy:metadata:calendartype',
81
            'theme' => 'privacy:metadata:theme',
82
            'timezone' => 'privacy:metadata:timezone',
83
            'firstaccess' => 'privacy:metadata:firstaccess',
84
            'lastaccess' => 'privacy:metadata:lastaccess',
85
            'lastlogin' => 'privacy:metadata:lastlogin',
86
            'currentlogin' => 'privacy:metadata:currentlogin',
87
            'lastip' => 'privacy:metadata:lastip',
88
            'secret' => 'privacy:metadata:secret',
89
            'picture' => 'privacy:metadata:picture',
90
            'description' => 'privacy:metadata:description',
91
            'maildigest' => 'privacy:metadata:maildigest',
92
            'maildisplay' => 'privacy:metadata:maildisplay',
93
            'autosubscribe' => 'privacy:metadata:autosubscribe',
94
            'trackforums' => 'privacy:metadata:trackforums',
95
            'timecreated' => 'privacy:metadata:timecreated',
96
            'timemodified' => 'privacy:metadata:timemodified',
97
            'trustbitmask' => 'privacy:metadata:trustbitmask',
98
            'imagealt' => 'privacy:metadata:imagealt',
99
            'lastnamephonetic' => 'privacy:metadata:lastnamephonetic',
100
            'firstnamephonetic' => 'privacy:metadata:firstnamephonetic',
101
            'middlename' => 'privacy:metadata:middlename',
102
            'alternatename' => 'privacy:metadata:alternatename',
103
            'moodlenetprofile' => 'privacy:metadata:moodlenetprofile'
104
        ];
105
 
106
        $passwordhistory = [
107
            'userid' => 'privacy:metadata:userid',
108
            'hash' => 'privacy:metadata:hash',
109
            'timecreated' => 'privacy:metadata:timecreated'
110
        ];
111
 
112
        $lastaccess = [
113
            'userid' => 'privacy:metadata:userid',
114
            'courseid' => 'privacy:metadata:courseid',
115
            'timeaccess' => 'privacy:metadata:timeaccess'
116
        ];
117
 
118
        $userpasswordresets = [
119
            'userid' => 'privacy:metadata:userid',
120
            'timerequested' => 'privacy:metadata:timerequested',
121
            'timererequested' => 'privacy:metadata:timererequested',
122
            'token' => 'privacy:metadata:token'
123
        ];
124
 
125
        $userdevices = [
126
            'userid' => 'privacy:metadata:userid',
127
            'appid' => 'privacy:metadata:appid',
128
            'name' => 'privacy:metadata:devicename',
129
            'model' => 'privacy:metadata:model',
130
            'platform' => 'privacy:metadata:platform',
131
            'version' => 'privacy:metadata:version',
132
            'pushid' => 'privacy:metadata:pushid',
133
            'uuid' => 'privacy:metadata:uuid',
134
            'timecreated' => 'privacy:metadata:timecreated',
135
            'timemodified' => 'privacy:metadata:timemodified'
136
        ];
137
 
138
        $usersessions = [
139
            'state' => 'privacy:metadata:state',
140
            'sid' => 'privacy:metadata:sid',
141
            'userid' => 'privacy:metadata:userid',
142
            'sessdata' => 'privacy:metadata:sessdata',
143
            'timecreated' => 'privacy:metadata:timecreated',
144
            'timemodified' => 'privacy:metadata:timemodified',
145
            'firstip' => 'privacy:metadata:firstip',
146
            'lastip' => 'privacy:metadata:lastip'
147
        ];
148
 
149
        $courserequest = [
150
            'fullname' => 'privacy:metadata:fullname',
151
            'shortname' => 'privacy:metadata:shortname',
152
            'summary' => 'privacy:metadata:summary',
153
            'category' => 'privacy:metadata:category',
154
            'reason' => 'privacy:metadata:reason',
155
            'requester' => 'privacy:metadata:requester'
156
        ];
157
 
158
        $mypages = [
159
            'userid' => 'privacy:metadata:my_pages:userid',
160
            'name' => 'privacy:metadata:my_pages:name',
161
            'private' => 'privacy:metadata:my_pages:private',
162
        ];
163
 
164
        $userpreferences = [
165
            'userid' => 'privacy:metadata:user_preferences:userid',
166
            'name' => 'privacy:metadata:user_preferences:name',
167
            'value' => 'privacy:metadata:user_preferences:value'
168
        ];
169
 
170
        $collection->add_database_table('user', $userfields, 'privacy:metadata:usertablesummary');
171
        $collection->add_database_table('user_password_history', $passwordhistory, 'privacy:metadata:passwordtablesummary');
172
        $collection->add_database_table('user_password_resets', $userpasswordresets, 'privacy:metadata:passwordresettablesummary');
173
        $collection->add_database_table('user_lastaccess', $lastaccess, 'privacy:metadata:lastaccesstablesummary');
174
        $collection->add_database_table('user_devices', $userdevices, 'privacy:metadata:devicetablesummary');
175
        $collection->add_database_table('course_request', $courserequest, 'privacy:metadata:requestsummary');
176
        $collection->add_database_table('sessions', $usersessions, 'privacy:metadata:sessiontablesummary');
177
        $collection->add_database_table('my_pages', $mypages, 'privacy:metadata:my_pages');
178
        $collection->add_database_table('user_preferences', $userpreferences, 'privacy:metadata:user_preferences');
179
        $collection->add_subsystem_link('core_files', [], 'privacy:metadata:filelink');
180
 
181
        $collection->add_user_preference(
182
            'core_user_welcome',
183
            'privacy:metadata:user_preference:core_user_welcome'
184
        );
185
 
186
        return $collection;
187
    }
188
 
189
    /**
190
     * Get the list of contexts that contain user information for the specified user.
191
     *
192
     * @param   int $userid The user to search.
193
     * @return  contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
194
     */
195
    public static function get_contexts_for_userid(int $userid): contextlist {
196
        $params = ['userid' => $userid, 'contextuser' => CONTEXT_USER];
197
        $sql = "SELECT id
198
                  FROM {context}
199
                 WHERE instanceid = :userid and contextlevel = :contextuser";
200
        $contextlist = new contextlist();
201
        $contextlist->add_from_sql($sql, $params);
202
        return $contextlist;
203
    }
204
 
205
    /**
206
     * Get the list of users within a specific context.
207
     *
208
     * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
209
     */
210
    public static function get_users_in_context(userlist $userlist) {
211
        $context = $userlist->get_context();
212
 
213
        if (!$context instanceof \context_user) {
214
            return;
215
        }
216
 
217
        $userlist->add_user($context->instanceid);
218
    }
219
 
220
    /**
221
     * Export all user data for the specified user, in the specified contexts.
222
     *
223
     * @param approved_contextlist $contextlist The approved contexts to export information for.
224
     */
225
    public static function export_user_data(approved_contextlist $contextlist) {
226
        $context = $contextlist->current();
227
        $user = \core_user::get_user($contextlist->get_user()->id);
228
        static::export_user($user, $context);
229
        static::export_password_history($user->id, $context);
230
        static::export_password_resets($user->id, $context);
231
        static::export_lastaccess($user->id, $context);
232
        static::export_course_requests($user->id, $context);
233
        static::export_user_devices($user->id, $context);
234
        static::export_user_session_data($user->id, $context);
235
    }
236
 
237
    /**
238
     * Delete all data for all users in the specified context.
239
     *
240
     * @param context $context The specific context to delete data for.
241
     */
242
    public static function delete_data_for_all_users_in_context(\context $context) {
243
        // Only delete data for a user context.
244
        if ($context->contextlevel == CONTEXT_USER) {
245
            static::delete_user_data($context->instanceid, $context);
246
        }
247
    }
248
 
249
    /**
250
     * Delete multiple users within a single context.
251
     *
252
     * @param approved_userlist $userlist The approved context and user information to delete information for.
253
     */
254
    public static function delete_data_for_users(approved_userlist $userlist) {
255
 
256
        $context = $userlist->get_context();
257
 
258
        if ($context instanceof \context_user) {
259
            static::delete_user_data($context->instanceid, $context);
260
        }
261
    }
262
 
263
    /**
264
     * Delete all user data for the specified user, in the specified contexts.
265
     *
266
     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
267
     */
268
    public static function delete_data_for_user(approved_contextlist $contextlist) {
269
        foreach ($contextlist as $context) {
270
            // Let's be super certain that we have the right information for this user here.
271
            if ($context->contextlevel == CONTEXT_USER && $contextlist->get_user()->id == $context->instanceid) {
272
                static::delete_user_data($contextlist->get_user()->id, $contextlist->current());
273
            }
274
        }
275
    }
276
 
277
    /**
278
     * Deletes non vital information about a user.
279
     *
280
     * @param  int      $userid  The user ID to delete
281
     * @param  \context $context The user context
282
     */
283
    protected static function delete_user_data(int $userid, \context $context) {
284
        global $DB;
285
 
286
        // Delete password history.
287
        $DB->delete_records('user_password_history', ['userid' => $userid]);
288
        // Delete last access.
289
        $DB->delete_records('user_lastaccess', ['userid' => $userid]);
290
        // Delete password resets.
291
        $DB->delete_records('user_password_resets', ['userid' => $userid]);
292
        // Delete user devices.
293
        $DB->delete_records('user_devices', ['userid' => $userid]);
294
        // Delete user course requests.
295
        $DB->delete_records('course_request', ['requester' => $userid]);
296
        // Delete sessions.
297
        $DB->delete_records('sessions', ['userid' => $userid]);
298
        // Do I delete user preferences? Seems like the right place to do it.
299
        $DB->delete_records('user_preferences', ['userid' => $userid]);
300
 
301
        // Delete all of the files for this user.
302
        $fs = get_file_storage();
303
        $fs->delete_area_files($context->id, 'user');
304
 
305
        // For the user record itself we only want to remove unnecessary data. We still need the core data to keep as a record
306
        // that we actually did follow the request to be forgotten.
307
        $user = \core_user::get_user($userid);
308
        // Update fields we wish to change to nothing.
309
        $user->deleted = 1;
310
        $user->idnumber = '';
311
        $user->emailstop = 0;
312
        $user->phone1 = '';
313
        $user->phone2 = '';
314
        $user->institution = '';
315
        $user->department = '';
316
        $user->address = '';
317
        $user->city = '';
318
        $user->country = '';
319
        $user->lang = '';
320
        $user->calendartype = '';
321
        $user->theme = '';
322
        $user->timezone = '';
323
        $user->firstaccess = 0;
324
        $user->lastaccess = 0;
325
        $user->lastlogin = 0;
326
        $user->currentlogin = 0;
327
        $user->lastip = 0;
328
        $user->secret = '';
329
        $user->picture = '';
330
        $user->description = '';
331
        $user->descriptionformat = 0;
332
        $user->mailformat = 0;
333
        $user->maildigest = 0;
334
        $user->maildisplay = 0;
335
        $user->autosubscribe = 0;
336
        $user->trackforums = 0;
337
        $user->timecreated = 0;
338
        $user->timemodified = 0;
339
        $user->trustbitmask = 0;
340
        $user->imagealt = '';
341
        $user->lastnamephonetic = '';
342
        $user->firstnamephonetic = '';
343
        $user->middlename = '';
344
        $user->alternatename = '';
345
        $DB->update_record('user', $user);
346
    }
347
 
348
    /**
349
     * Export core user data.
350
     *
351
     * @param  \stdClass $user The user object.
352
     * @param  \context $context The user context.
353
     */
354
    protected static function export_user(\stdClass $user, \context $context) {
355
        $data = (object) [
356
            'auth' => $user->auth,
357
            'confirmed' => transform::yesno($user->confirmed),
358
            'policyagreed' => transform::yesno($user->policyagreed),
359
            'deleted' => transform::yesno($user->deleted),
360
            'suspended' => transform::yesno($user->suspended),
361
            'username' => $user->username,
362
            'idnumber' => $user->idnumber,
363
            'firstname' => format_string($user->firstname, true, ['context' => $context]),
364
            'lastname' => format_string($user->lastname, true, ['context' => $context]),
365
            'email' => $user->email,
366
            'emailstop' => transform::yesno($user->emailstop),
367
            'phone1' => format_string($user->phone1, true, ['context' => $context]),
368
            'phone2' => format_string($user->phone2, true, ['context' => $context]),
369
            'institution' => format_string($user->institution, true, ['context' => $context]),
370
            'department' => format_string($user->department, true, ['context' => $context]),
371
            'address' => format_string($user->address, true, ['context' => $context]),
372
            'city' => format_string($user->city, true, ['context' => $context]),
373
            'country' => format_string($user->country, true, ['context' => $context]),
374
            'lang' => $user->lang,
375
            'calendartype' => $user->calendartype,
376
            'theme' => $user->theme,
377
            'timezone' => $user->timezone,
378
            'firstaccess' => $user->firstaccess ? transform::datetime($user->firstaccess) : null,
379
            'lastaccess' => $user->lastaccess ? transform::datetime($user->lastaccess) : null,
380
            'lastlogin' => $user->lastlogin ? transform::datetime($user->lastlogin) : null,
381
            'currentlogin' => $user->currentlogin ? transform::datetime($user->currentlogin) : null,
382
            'lastip' => $user->lastip,
383
            'secret' => $user->secret,
384
            'picture' => $user->picture,
385
            'description' => format_text(
386
                writer::with_context($context)->rewrite_pluginfile_urls(
387
                    [],
388
                    'user',
389
                    'profile',
390
                    '',
391
                    $user->description
392
                ), $user->descriptionformat, ['context' => $context]),
393
            'maildigest' => transform::yesno($user->maildigest),
394
            'maildisplay' => $user->maildisplay,
395
            'autosubscribe' => transform::yesno($user->autosubscribe),
396
            'trackforums' => transform::yesno($user->trackforums),
397
            'timecreated' => transform::datetime($user->timecreated),
398
            'timemodified' => transform::datetime($user->timemodified),
399
            'imagealt' => format_string($user->imagealt, true, ['context' => $context]),
400
            'lastnamephonetic' => format_string($user->lastnamephonetic, true, ['context' => $context]),
401
            'firstnamephonetic' => format_string($user->firstnamephonetic, true, ['context' => $context]),
402
            'middlename' => format_string($user->middlename, true, ['context' => $context]),
403
            'alternatename'  => format_string($user->alternatename, true, ['context' => $context])
404
        ];
405
 
406
        writer::with_context($context)->export_area_files([], 'user', 'profile', 0)
407
                ->export_data([], $data);
408
        // Export profile images.
409
        writer::with_context($context)->export_area_files([get_string('privacy:profileimagespath', 'user')], 'user', 'icon', 0);
410
        // Export private files.
411
        writer::with_context($context)->export_area_files([get_string('privacy:privatefilespath', 'user')], 'user', 'private', 0);
412
        // Export draft files.
413
        writer::with_context($context)->export_area_files([get_string('privacy:draftfilespath', 'user')], 'user', 'draft', false);
414
    }
415
 
416
    /**
417
     * Export information about the last time a user accessed a course.
418
     *
419
     * @param  int $userid The user ID.
420
     * @param  \context $context The user context.
421
     */
422
    protected static function export_lastaccess(int $userid, \context $context) {
423
        global $DB;
424
        $sql = "SELECT c.id, c.fullname, ul.timeaccess
425
                  FROM {user_lastaccess} ul
426
                  JOIN {course} c ON c.id = ul.courseid
427
                 WHERE ul.userid = :userid";
428
        $params = ['userid' => $userid];
429
        $records = $DB->get_records_sql($sql, $params);
430
        if (!empty($records)) {
431
            $lastaccess = (object) array_map(function($record) use ($context) {
432
                return [
433
                    'course_name' => format_string($record->fullname, true, ['context' => $context]),
434
                    'timeaccess' => transform::datetime($record->timeaccess)
435
                ];
436
            }, $records);
437
            writer::with_context($context)->export_data([get_string('privacy:lastaccesspath', 'user')], $lastaccess);
438
        }
439
    }
440
 
441
    /**
442
     * Exports information about password resets.
443
     *
444
     * @param  int $userid The user ID
445
     * @param  \context $context Context for this user.
446
     */
447
    protected static function export_password_resets(int $userid, \context $context) {
448
        global $DB;
449
        $records = $DB->get_records('user_password_resets', ['userid' => $userid]);
450
        if (!empty($records)) {
451
            $passwordresets = (object) array_map(function($record) {
452
                return [
453
                    'timerequested' => transform::datetime($record->timerequested),
454
                    'timererequested' => transform::datetime($record->timererequested)
455
                ];
456
            }, $records);
457
            writer::with_context($context)->export_data([get_string('privacy:passwordresetpath', 'user')], $passwordresets);
458
        }
459
    }
460
 
461
    /**
462
     * Exports information about the user's mobile devices.
463
     *
464
     * @param  int $userid The user ID.
465
     * @param  \context $context Context for this user.
466
     */
467
    protected static function export_user_devices(int $userid, \context $context) {
468
        global $DB;
469
        $records = $DB->get_records('user_devices', ['userid' => $userid]);
470
        if (!empty($records)) {
471
            $userdevices = (object) array_map(function($record) {
472
                return [
473
                    'appid' => $record->appid,
474
                    'name' => $record->name,
475
                    'model' => $record->model,
476
                    'platform' => $record->platform,
477
                    'version' => $record->version,
478
                    'timecreated' => transform::datetime($record->timecreated),
479
                    'timemodified' => transform::datetime($record->timemodified)
480
                ];
481
            }, $records);
482
            writer::with_context($context)->export_data([get_string('privacy:devicespath', 'user')], $userdevices);
483
        }
484
    }
485
 
486
    /**
487
     * Exports information about course requests this user made.
488
     *
489
     * @param  int    $userid  The user ID.
490
     * @param  \context $context The context object
491
     */
492
    protected static function export_course_requests(int $userid, \context $context) {
493
        global $DB;
494
        $sql = "SELECT cr.shortname, cr.fullname, cr.summary, cc.name AS category, cr.reason
495
                  FROM {course_request} cr
496
                  JOIN {course_categories} cc ON cr.category = cc.id
497
                 WHERE cr.requester = :userid";
498
        $params = ['userid' => $userid];
499
        $records = $DB->get_records_sql($sql, $params);
500
        if ($records) {
501
            writer::with_context($context)->export_data([get_string('privacy:courserequestpath', 'user')], (object) $records);
502
        }
503
    }
504
 
505
    /**
506
     * Get details about the user's password history.
507
     *
508
     * @param int $userid The user ID that we are getting the password history for.
509
     * @param \context $context the user context.
510
     */
511
    protected static function export_password_history(int $userid, \context $context) {
512
        global $DB;
513
 
514
        // Just provide a count of how many entries we have.
515
        $recordcount = $DB->count_records('user_password_history', ['userid' => $userid]);
516
        if ($recordcount) {
517
            $passwordhistory = (object) ['password_history_count' => $recordcount];
518
            writer::with_context($context)->export_data([get_string('privacy:passwordhistorypath', 'user')], $passwordhistory);
519
        }
520
    }
521
 
522
    /**
523
     * Exports information about the user's session.
524
     *
525
     * @param  int $userid The user ID.
526
     * @param  \context $context The context for this user.
527
     */
528
    protected static function export_user_session_data(int $userid, \context $context) {
529
        global $DB, $SESSION;
530
 
531
        $records = $DB->get_records('sessions', ['userid' => $userid]);
532
        if (!empty($records)) {
533
            $sessiondata = (object) array_map(function($record) {
534
                return [
535
                    'state' => $record->state,
536
                    'sessdata' => ($record->sessdata !== null) ? base64_decode($record->sessdata) : '',
537
                    'timecreated' => transform::datetime($record->timecreated),
538
                    'timemodified' => transform::datetime($record->timemodified),
539
                    'firstip' => $record->firstip,
540
                    'lastip' => $record->lastip
541
                ];
542
            }, $records);
543
            writer::with_context($context)->export_data([get_string('privacy:sessionpath', 'user')], $sessiondata);
544
        }
545
    }
546
 
547
    /**
548
     * Export all user preferences for the plugin.
549
     *
550
     * @param   int $userid The userid of the user whose data is to be exported.
551
     */
552
    public static function export_user_preferences(int $userid) {
553
        $userwelcomepreference = get_user_preferences('core_user_welcome', null, $userid);
554
 
555
        if ($userwelcomepreference !== null) {
556
            writer::export_user_preference(
557
                'core_user',
558
                'core_user_welcome',
559
                $userwelcomepreference,
560
                get_string('privacy:metadata:user_preference:core_user_welcome', 'core_user')
561
            );
562
        }
563
    }
564
 
565
}