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 Subsystem implementation for mod_forum.
19
 *
20
 * @package    mod_forum
21
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace mod_forum\privacy;
26
 
27
use core_grades\component_gradeitem as gradeitem;
28
use \core_privacy\local\request\userlist;
29
use \core_privacy\local\request\approved_contextlist;
30
use \core_privacy\local\request\approved_userlist;
31
use \core_privacy\local\request\deletion_criteria;
32
use \core_privacy\local\request\writer;
33
use \core_privacy\local\request\helper as request_helper;
34
use \core_privacy\local\metadata\collection;
35
use \core_privacy\local\request\transform;
36
use tool_dataprivacy\context_instance;
37
 
38
defined('MOODLE_INTERNAL') || die();
39
 
40
require_once($CFG->dirroot . '/grade/grading/lib.php');
41
 
42
/**
43
 * Implementation of the privacy subsystem plugin provider for the forum activity module.
44
 *
45
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
46
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47
 */
48
class provider implements
49
    // This plugin has data.
50
    \core_privacy\local\metadata\provider,
51
 
52
    // This plugin currently implements the original plugin\provider interface.
53
    \core_privacy\local\request\plugin\provider,
54
 
55
    // This plugin is capable of determining which users have data within it.
56
    \core_privacy\local\request\core_userlist_provider,
57
 
58
    // This plugin has some sitewide user preferences to export.
59
    \core_privacy\local\request\user_preference_provider
60
{
61
 
62
    use subcontext_info;
63
 
64
    /**
65
     * Returns meta data about this system.
66
     *
67
     * @param   collection     $items The initialised collection to add items to.
68
     * @return  collection     A listing of user data stored through this system.
69
     */
70
    public static function get_metadata(collection $items): collection {
71
        // The 'forum' table does not store any specific user data.
72
        $items->add_database_table('forum_digests', [
73
            'forum' => 'privacy:metadata:forum_digests:forum',
74
            'userid' => 'privacy:metadata:forum_digests:userid',
75
            'maildigest' => 'privacy:metadata:forum_digests:maildigest',
76
        ], 'privacy:metadata:forum_digests');
77
 
78
        // The 'forum_discussions' table stores the metadata about each forum discussion.
79
        $items->add_database_table('forum_discussions', [
80
            'name' => 'privacy:metadata:forum_discussions:name',
81
            'userid' => 'privacy:metadata:forum_discussions:userid',
82
            'assessed' => 'privacy:metadata:forum_discussions:assessed',
83
            'timemodified' => 'privacy:metadata:forum_discussions:timemodified',
84
            'usermodified' => 'privacy:metadata:forum_discussions:usermodified',
85
        ], 'privacy:metadata:forum_discussions');
86
 
87
        // The 'forum_discussion_subs' table stores information about which discussions a user is subscribed to.
88
        $items->add_database_table('forum_discussion_subs', [
89
            'discussionid' => 'privacy:metadata:forum_discussion_subs:discussionid',
90
            'preference' => 'privacy:metadata:forum_discussion_subs:preference',
91
            'userid' => 'privacy:metadata:forum_discussion_subs:userid',
92
        ], 'privacy:metadata:forum_discussion_subs');
93
 
94
        // The 'forum_posts' table stores the metadata about each forum discussion.
95
        $items->add_database_table('forum_posts', [
96
            'discussion' => 'privacy:metadata:forum_posts:discussion',
97
            'parent' => 'privacy:metadata:forum_posts:parent',
98
            'created' => 'privacy:metadata:forum_posts:created',
99
            'modified' => 'privacy:metadata:forum_posts:modified',
100
            'subject' => 'privacy:metadata:forum_posts:subject',
101
            'message' => 'privacy:metadata:forum_posts:message',
102
            'userid' => 'privacy:metadata:forum_posts:userid',
103
            'privatereplyto' => 'privacy:metadata:forum_posts:privatereplyto',
104
        ], 'privacy:metadata:forum_posts');
105
 
106
        // The 'forum_queue' table contains user data, but it is only a temporary cache of other data.
107
        // We should not need to export it as it does not allow profiling of a user.
108
 
109
        // The 'forum_read' table stores data about which forum posts have been read by each user.
110
        $items->add_database_table('forum_read', [
111
            'userid' => 'privacy:metadata:forum_read:userid',
112
            'discussionid' => 'privacy:metadata:forum_read:discussionid',
113
            'postid' => 'privacy:metadata:forum_read:postid',
114
            'firstread' => 'privacy:metadata:forum_read:firstread',
115
            'lastread' => 'privacy:metadata:forum_read:lastread',
116
        ], 'privacy:metadata:forum_read');
117
 
118
        // The 'forum_subscriptions' table stores information about which forums a user is subscribed to.
119
        $items->add_database_table('forum_subscriptions', [
120
            'userid' => 'privacy:metadata:forum_subscriptions:userid',
121
            'forum' => 'privacy:metadata:forum_subscriptions:forum',
122
        ], 'privacy:metadata:forum_subscriptions');
123
 
124
        // The 'forum_subscriptions' table stores information about which forums a user is subscribed to.
125
        $items->add_database_table('forum_track_prefs', [
126
            'userid' => 'privacy:metadata:forum_track_prefs:userid',
127
            'forumid' => 'privacy:metadata:forum_track_prefs:forumid',
128
        ], 'privacy:metadata:forum_track_prefs');
129
 
130
        // The 'forum_queue' table stores temporary data that is not exported/deleted.
131
        $items->add_database_table('forum_queue', [
132
            'userid' => 'privacy:metadata:forum_queue:userid',
133
            'discussionid' => 'privacy:metadata:forum_queue:discussionid',
134
            'postid' => 'privacy:metadata:forum_queue:postid',
135
            'timemodified' => 'privacy:metadata:forum_queue:timemodified'
136
        ], 'privacy:metadata:forum_queue');
137
 
138
        // The 'forum_grades' table stores grade data.
139
        $items->add_database_table('forum_grades', [
140
            'userid' => 'privacy:metadata:forum_grades:userid',
141
            'forum' => 'privacy:metadata:forum_grades:forum',
142
            'grade' => 'privacy:metadata:forum_grades:grade',
143
        ], 'privacy:metadata:forum_grades');
144
 
145
        // Forum posts can be tagged and rated.
146
        $items->link_subsystem('core_tag', 'privacy:metadata:core_tag');
147
        $items->link_subsystem('core_rating', 'privacy:metadata:core_rating');
148
 
149
        // There are several user preferences.
150
        $items->add_user_preference('maildigest', 'privacy:metadata:preference:maildigest');
151
        $items->add_user_preference('autosubscribe', 'privacy:metadata:preference:autosubscribe');
152
        $items->add_user_preference('trackforums', 'privacy:metadata:preference:trackforums');
153
        $items->add_user_preference('markasreadonnotification', 'privacy:metadata:preference:markasreadonnotification');
154
        $items->add_user_preference('forum_discussionlistsortorder',
155
            'privacy:metadata:preference:forum_discussionlistsortorder');
156
 
157
        return $items;
158
    }
159
 
160
    /**
161
     * Get the list of contexts that contain user information for the specified user.
162
     *
163
     * In the case of forum, that is any forum where the user has made any post, rated any content, or has any preferences.
164
     *
165
     * @param   int         $userid     The user to search.
166
     * @return  contextlist $contextlist  The contextlist containing the list of contexts used in this plugin.
167
     */
168
    public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist {
169
        $contextlist = new \core_privacy\local\request\contextlist();
170
 
171
        $params = [
172
            'modname'       => 'forum',
173
            'contextlevel'  => CONTEXT_MODULE,
174
            'userid'        => $userid,
175
        ];
176
 
177
        // Discussion creators.
178
        $sql = "SELECT c.id
179
                  FROM {context} c
180
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
181
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
182
                  JOIN {forum} f ON f.id = cm.instance
183
                  JOIN {forum_discussions} d ON d.forum = f.id
184
                 WHERE d.userid = :userid
185
        ";
186
        $contextlist->add_from_sql($sql, $params);
187
 
188
        // Post authors.
189
        $sql = "SELECT c.id
190
                  FROM {context} c
191
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
192
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
193
                  JOIN {forum} f ON f.id = cm.instance
194
                  JOIN {forum_discussions} d ON d.forum = f.id
195
                  JOIN {forum_posts} p ON p.discussion = d.id
196
                 WHERE p.userid = :userid
197
        ";
198
        $contextlist->add_from_sql($sql, $params);
199
 
200
        // Forum digest records.
201
        $sql = "SELECT c.id
202
                  FROM {context} c
203
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
204
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
205
                  JOIN {forum} f ON f.id = cm.instance
206
                  JOIN {forum_digests} dig ON dig.forum = f.id
207
                 WHERE dig.userid = :userid
208
        ";
209
        $contextlist->add_from_sql($sql, $params);
210
 
211
        // Forum subscriptions.
212
        $sql = "SELECT c.id
213
                  FROM {context} c
214
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
215
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
216
                  JOIN {forum} f ON f.id = cm.instance
217
                  JOIN {forum_subscriptions} sub ON sub.forum = f.id
218
                 WHERE sub.userid = :userid
219
        ";
220
        $contextlist->add_from_sql($sql, $params);
221
 
222
        // Discussion subscriptions.
223
        $sql = "SELECT c.id
224
                  FROM {context} c
225
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
226
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
227
                  JOIN {forum} f ON f.id = cm.instance
228
                  JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id
229
                 WHERE dsub.userid = :userid
230
        ";
231
        $contextlist->add_from_sql($sql, $params);
232
 
233
        // Discussion tracking preferences.
234
        $sql = "SELECT c.id
235
                  FROM {context} c
236
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
237
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
238
                  JOIN {forum} f ON f.id = cm.instance
239
                  JOIN {forum_track_prefs} pref ON pref.forumid = f.id
240
                 WHERE pref.userid = :userid
241
        ";
242
        $contextlist->add_from_sql($sql, $params);
243
 
244
        // Discussion read records.
245
        $sql = "SELECT c.id
246
                  FROM {context} c
247
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
248
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
249
                  JOIN {forum} f ON f.id = cm.instance
250
                  JOIN {forum_read} hasread ON hasread.forumid = f.id
251
                 WHERE hasread.userid = :userid
252
        ";
253
        $contextlist->add_from_sql($sql, $params);
254
 
255
        // Rating authors.
256
        $ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid, true);
257
        $sql = "SELECT c.id
258
                  FROM {context} c
259
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
260
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
261
                  JOIN {forum} f ON f.id = cm.instance
262
                  JOIN {forum_discussions} d ON d.forum = f.id
263
                  JOIN {forum_posts} p ON p.discussion = d.id
264
                  {$ratingsql->join}
265
                 WHERE {$ratingsql->userwhere}
266
        ";
267
        $params += $ratingsql->params;
268
        $contextlist->add_from_sql($sql, $params);
269
 
270
        // Forum grades.
271
        $sql = "SELECT c.id
272
                  FROM {context} c
273
                  JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
274
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modname
275
                  JOIN {forum} f ON f.id = cm.instance
276
                  JOIN {forum_grades} fg ON fg.forum = f.id
277
                 WHERE fg.userid = :userid
278
        ";
279
        $contextlist->add_from_sql($sql, $params);
280
 
281
        return $contextlist;
282
    }
283
 
284
    /**
285
     * Get the list of users within a specific context.
286
     *
287
     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
288
     */
289
    public static function get_users_in_context(userlist $userlist) {
290
        $context = $userlist->get_context();
291
 
292
        if (!is_a($context, \context_module::class)) {
293
            return;
294
        }
295
 
296
        $params = [
297
            'instanceid'    => $context->instanceid,
298
            'modulename'    => 'forum',
299
        ];
300
 
301
        // Discussion authors.
302
        $sql = "SELECT d.userid
303
                  FROM {course_modules} cm
304
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
305
                  JOIN {forum} f ON f.id = cm.instance
306
                  JOIN {forum_discussions} d ON d.forum = f.id
307
                 WHERE cm.id = :instanceid";
308
        $userlist->add_from_sql('userid', $sql, $params);
309
 
310
        // Forum authors.
311
        $sql = "SELECT p.userid
312
                  FROM {course_modules} cm
313
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
314
                  JOIN {forum} f ON f.id = cm.instance
315
                  JOIN {forum_discussions} d ON d.forum = f.id
316
                  JOIN {forum_posts} p ON d.id = p.discussion
317
                 WHERE cm.id = :instanceid";
318
        $userlist->add_from_sql('userid', $sql, $params);
319
 
320
        // Forum post ratings.
321
        $sql = "SELECT p.id
322
                  FROM {course_modules} cm
323
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
324
                  JOIN {forum} f ON f.id = cm.instance
325
                  JOIN {forum_discussions} d ON d.forum = f.id
326
                  JOIN {forum_posts} p ON d.id = p.discussion
327
                 WHERE cm.id = :instanceid";
328
        \core_rating\privacy\provider::get_users_in_context_from_sql($userlist, 'rat', 'mod_forum', 'post', $sql, $params);
329
 
330
        // Forum Digest settings.
331
        $sql = "SELECT dig.userid
332
                  FROM {course_modules} cm
333
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
334
                  JOIN {forum} f ON f.id = cm.instance
335
                  JOIN {forum_digests} dig ON dig.forum = f.id
336
                 WHERE cm.id = :instanceid";
337
        $userlist->add_from_sql('userid', $sql, $params);
338
 
339
        // Forum Subscriptions.
340
        $sql = "SELECT sub.userid
341
                  FROM {course_modules} cm
342
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
343
                  JOIN {forum} f ON f.id = cm.instance
344
                  JOIN {forum_subscriptions} sub ON sub.forum = f.id
345
                 WHERE cm.id = :instanceid";
346
        $userlist->add_from_sql('userid', $sql, $params);
347
 
348
        // Discussion subscriptions.
349
        $sql = "SELECT dsub.userid
350
                  FROM {course_modules} cm
351
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
352
                  JOIN {forum} f ON f.id = cm.instance
353
                  JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id
354
                 WHERE cm.id = :instanceid";
355
        $userlist->add_from_sql('userid', $sql, $params);
356
 
357
        // Read Posts.
358
        $sql = "SELECT hasread.userid
359
                  FROM {course_modules} cm
360
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
361
                  JOIN {forum} f ON f.id = cm.instance
362
                  JOIN {forum_read} hasread ON hasread.forumid = f.id
363
                 WHERE cm.id = :instanceid";
364
        $userlist->add_from_sql('userid', $sql, $params);
365
 
366
        // Tracking Preferences.
367
        $sql = "SELECT pref.userid
368
                  FROM {course_modules} cm
369
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
370
                  JOIN {forum} f ON f.id = cm.instance
371
                  JOIN {forum_track_prefs} pref ON pref.forumid = f.id
372
                 WHERE cm.id = :instanceid";
373
        $userlist->add_from_sql('userid', $sql, $params);
374
 
375
        // Forum grades.
376
        $sql = "SELECT fg.userid
377
                  FROM {course_modules} cm
378
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
379
                  JOIN {forum} f ON f.id = cm.instance
380
                  JOIN {forum_grades} fg ON fg.forum = f.id
381
                 WHERE cm.id = :instanceid";
382
        $userlist->add_from_sql('userid', $sql, $params);
383
    }
384
 
385
    /**
386
     * Store all user preferences for the plugin.
387
     *
388
     * @param   int         $userid The userid of the user whose data is to be exported.
389
     */
390
    public static function export_user_preferences(int $userid) {
391
        $user = \core_user::get_user($userid);
392
 
393
        switch ($user->maildigest) {
394
            case 1:
395
                $digestdescription = get_string('emaildigestcomplete');
396
                break;
397
            case 2:
398
                $digestdescription = get_string('emaildigestsubjects');
399
                break;
400
            case 0:
401
            default:
402
                $digestdescription = get_string('emaildigestoff');
403
                break;
404
        }
405
        writer::export_user_preference('mod_forum', 'maildigest', $user->maildigest, $digestdescription);
406
 
407
        switch ($user->autosubscribe) {
408
            case 0:
409
                $subscribedescription = get_string('autosubscribeno');
410
                break;
411
            case 1:
412
            default:
413
                $subscribedescription = get_string('autosubscribeyes');
414
                break;
415
        }
416
        writer::export_user_preference('mod_forum', 'autosubscribe', $user->autosubscribe, $subscribedescription);
417
 
418
        switch ($user->trackforums) {
419
            case 0:
420
                $trackforumdescription = get_string('trackforumsno');
421
                break;
422
            case 1:
423
            default:
424
                $trackforumdescription = get_string('trackforumsyes');
425
                break;
426
        }
427
        writer::export_user_preference('mod_forum', 'trackforums', $user->trackforums, $trackforumdescription);
428
 
429
        $markasreadonnotification = get_user_preferences('markasreadonnotification', null, $user->id);
430
        if (null !== $markasreadonnotification) {
431
            switch ($markasreadonnotification) {
432
                case 0:
433
                    $markasreadonnotificationdescription = get_string('markasreadonnotificationno', 'mod_forum');
434
                    break;
435
                case 1:
436
                default:
437
                    $markasreadonnotificationdescription = get_string('markasreadonnotificationyes', 'mod_forum');
438
                    break;
439
            }
440
            writer::export_user_preference('mod_forum', 'markasreadonnotification', $markasreadonnotification,
441
                    $markasreadonnotificationdescription);
442
        }
443
 
444
        $vaultfactory = \mod_forum\local\container::get_vault_factory();
445
        $discussionlistvault = $vaultfactory->get_discussions_in_forum_vault();
446
        $discussionlistsortorder = get_user_preferences('forum_discussionlistsortorder',
447
            $discussionlistvault::SORTORDER_LASTPOST_DESC, $user->id);
448
        switch ($discussionlistsortorder) {
449
            case $discussionlistvault::SORTORDER_LASTPOST_DESC:
450
                $discussionlistsortorderdescription = get_string('discussionlistsortbylastpostdesc',
451
                    'mod_forum');
452
                break;
453
            case $discussionlistvault::SORTORDER_LASTPOST_ASC:
454
                $discussionlistsortorderdescription = get_string('discussionlistsortbylastpostasc',
455
                    'mod_forum');
456
                break;
457
            case $discussionlistvault::SORTORDER_CREATED_DESC:
458
                $discussionlistsortorderdescription = get_string('discussionlistsortbycreateddesc',
459
                    'mod_forum');
460
                break;
461
            case $discussionlistvault::SORTORDER_CREATED_ASC:
462
                $discussionlistsortorderdescription = get_string('discussionlistsortbycreatedasc',
463
                    'mod_forum');
464
                break;
465
            case $discussionlistvault::SORTORDER_REPLIES_DESC:
466
                $discussionlistsortorderdescription = get_string('discussionlistsortbyrepliesdesc',
467
                    'mod_forum');
468
                break;
469
            case $discussionlistvault::SORTORDER_REPLIES_ASC:
470
                $discussionlistsortorderdescription = get_string('discussionlistsortbyrepliesasc',
471
                    'mod_forum');
472
                break;
473
            case $discussionlistvault::SORTORDER_DISCUSSION_DESC:
474
                $discussionlistsortorderdescription = get_string('discussionlistsortbydiscussiondesc',
475
                    'mod_forum');
476
                break;
477
            case $discussionlistvault::SORTORDER_DISCUSSION_ASC:
478
                $discussionlistsortorderdescription = get_string('discussionlistsortbydiscussionasc',
479
                    'mod_forum');
480
                break;
481
            case $discussionlistvault::SORTORDER_STARTER_DESC:
482
                $discussionlistsortorderdescription = get_string('discussionlistsortbystarterdesc',
483
                    'mod_forum');
484
                break;
485
            case $discussionlistvault::SORTORDER_STARTER_ASC:
486
                $discussionlistsortorderdescription = get_string('discussionlistsortbystarterasc',
487
                    'mod_forum');
488
                break;
489
            case $discussionlistvault::SORTORDER_GROUP_DESC:
490
                $discussionlistsortorderdescription = get_string('discussionlistsortbygroupdesc',
491
                    'mod_forum');
492
                break;
493
            case $discussionlistvault::SORTORDER_GROUP_ASC:
494
                $discussionlistsortorderdescription = get_string('discussionlistsortbygroupasc',
495
                    'mod_forum');
496
                break;
497
        }
498
        writer::export_user_preference('mod_forum', 'forum_discussionlistsortorder',
499
            $discussionlistsortorder, $discussionlistsortorderdescription);
500
    }
501
 
502
 
503
    /**
504
     * Export all user data for the specified user, in the specified contexts.
505
     *
506
     * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
507
     */
508
    public static function export_user_data(approved_contextlist $contextlist) {
509
        global $DB;
510
 
511
        if (empty($contextlist)) {
512
            return;
513
        }
514
 
515
        $user = $contextlist->get_user();
516
        $userid = $user->id;
517
 
518
        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
519
        $params = $contextparams;
520
 
521
        // Digested forums.
522
        $sql = "SELECT
523
                    c.id AS contextid,
524
                    dig.maildigest AS maildigest
525
                  FROM {context} c
526
                  JOIN {course_modules} cm ON cm.id = c.instanceid
527
                  JOIN {forum} f ON f.id = cm.instance
528
                  JOIN {forum_digests} dig ON dig.forum = f.id
529
                 WHERE (
530
                    dig.userid = :userid AND
531
                    c.id {$contextsql}
532
                )
533
        ";
534
        $params['userid'] = $userid;
535
        $digests = $DB->get_records_sql_menu($sql, $params);
536
 
537
        // Forum subscriptions.
538
        $sql = "SELECT
539
                    c.id AS contextid,
540
                    sub.userid AS subscribed
541
                  FROM {context} c
542
                  JOIN {course_modules} cm ON cm.id = c.instanceid
543
                  JOIN {forum} f ON f.id = cm.instance
544
                  JOIN {forum_subscriptions} sub ON sub.forum = f.id
545
                 WHERE (
546
                    sub.userid = :userid AND
547
                    c.id {$contextsql}
548
                )
549
        ";
550
        $params['userid'] = $userid;
551
        $subscriptions = $DB->get_records_sql_menu($sql, $params);
552
 
553
        // Tracked forums.
554
        $sql = "SELECT
555
                    c.id AS contextid,
556
                    pref.userid AS tracked
557
                  FROM {context} c
558
                  JOIN {course_modules} cm ON cm.id = c.instanceid
559
                  JOIN {forum} f ON f.id = cm.instance
560
                  JOIN {forum_track_prefs} pref ON pref.forumid = f.id
561
                 WHERE (
562
                    pref.userid = :userid AND
563
                    c.id {$contextsql}
564
                )
565
        ";
566
        $params['userid'] = $userid;
567
        $tracked = $DB->get_records_sql_menu($sql, $params);
568
 
569
        // Forum grades.
570
        $sql = "SELECT
571
                    c.id AS contextid,
572
                    fg.grade AS grade,
573
                    f.grade_forum AS gradetype
574
                  FROM {context} c
575
                  JOIN {course_modules} cm ON cm.id = c.instanceid
576
                  JOIN {forum} f ON f.id = cm.instance
577
                  JOIN {forum_grades} fg ON fg.forum = f.id
578
                 WHERE (
579
                    fg.userid = :userid AND
580
                    c.id {$contextsql}
581
                )
582
        ";
583
        $params['userid'] = $userid;
584
        $grades = $DB->get_records_sql_menu($sql, $params);
585
 
586
        $sql = "SELECT
587
                    c.id AS contextid,
588
                    f.*,
589
                    cm.id AS cmid
590
                  FROM {context} c
591
                  JOIN {course_modules} cm ON cm.id = c.instanceid
592
                  JOIN {forum} f ON f.id = cm.instance
593
                 WHERE (
594
                    c.id {$contextsql}
595
                )
596
        ";
597
 
598
        $params += $contextparams;
599
 
600
        // Keep a mapping of forumid to contextid.
601
        $mappings = [];
602
 
603
        $forums = $DB->get_recordset_sql($sql, $params);
604
        foreach ($forums as $forum) {
605
            $mappings[$forum->id] = $forum->contextid;
606
 
607
            $context = \context::instance_by_id($mappings[$forum->id]);
608
 
609
            // Store the main forum data.
610
            $data = request_helper::get_context_data($context, $user);
611
            writer::with_context($context)
612
                ->export_data([], $data);
613
            request_helper::export_context_files($context, $user);
614
 
615
            // Store relevant metadata about this forum instance.
616
            if (isset($digests[$forum->contextid])) {
617
                static::export_digest_data($userid, $forum, $digests[$forum->contextid]);
618
            }
619
            if (isset($subscriptions[$forum->contextid])) {
620
                static::export_subscription_data($userid, $forum, $subscriptions[$forum->contextid]);
621
            }
622
            if (isset($tracked[$forum->contextid])) {
623
                static::export_tracking_data($userid, $forum, $tracked[$forum->contextid]);
624
            }
625
            if (isset($grades[$forum->contextid])) {
626
                static::export_grading_data($userid, $forum, $grades[$forum->contextid]);
627
            }
628
        }
629
        $forums->close();
630
 
631
        if (!empty($mappings)) {
632
            // Store all discussion data for this forum.
633
            static::export_discussion_data($userid, $mappings);
634
 
635
            // Store all post data for this forum.
636
            static::export_all_posts($userid, $mappings);
637
        }
638
    }
639
 
640
    /**
641
     * Store all information about all discussions that we have detected this user to have access to.
642
     *
643
     * @param   int         $userid The userid of the user whose data is to be exported.
644
     * @param   array       $mappings A list of mappings from forumid => contextid.
645
     * @return  array       Which forums had data written for them.
646
     */
647
    protected static function export_discussion_data(int $userid, array $mappings) {
648
        global $DB;
649
 
650
        // Find all of the discussions, and discussion subscriptions for this forum.
651
        list($foruminsql, $forumparams) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);
652
        $sql = "SELECT
653
                    d.*,
654
                    g.name as groupname,
655
                    dsub.preference
656
                  FROM {forum} f
657
                  JOIN {forum_discussions} d ON d.forum = f.id
658
             LEFT JOIN {groups} g ON g.id = d.groupid
659
             LEFT JOIN {forum_discussion_subs} dsub ON dsub.discussion = d.id AND dsub.userid = :dsubuserid
660
             LEFT JOIN {forum_posts} p ON p.discussion = d.id
661
                 WHERE f.id {$foruminsql}
662
                   AND (
663
                        d.userid    = :discussionuserid OR
664
                        p.userid    = :postuserid OR
665
                        dsub.id IS NOT NULL
666
                   )
667
        ";
668
 
669
        $params = [
670
            'postuserid'        => $userid,
671
            'discussionuserid'  => $userid,
672
            'dsubuserid'        => $userid,
673
        ];
674
        $params += $forumparams;
675
 
676
        // Keep track of the forums which have data.
677
        $forumswithdata = [];
678
 
679
        $discussions = $DB->get_recordset_sql($sql, $params);
680
        foreach ($discussions as $discussion) {
681
            // No need to take timestart into account as the user has some involvement already.
682
            // Ignore discussion timeend as it should not block access to user data.
683
            $forumswithdata[$discussion->forum] = true;
684
            $context = \context::instance_by_id($mappings[$discussion->forum]);
685
 
686
            // Store related metadata for this discussion.
687
            static::export_discussion_subscription_data($userid, $context, $discussion);
688
 
689
            $discussiondata = (object) [
690
                'name' => format_string($discussion->name, true),
691
                'pinned' => transform::yesno((bool) $discussion->pinned),
692
                'timemodified' => transform::datetime($discussion->timemodified),
693
                'usermodified' => transform::datetime($discussion->usermodified),
694
                'creator_was_you' => transform::yesno($discussion->userid == $userid),
695
            ];
696
 
697
            // Store the discussion content.
698
            writer::with_context($context)
699
                ->export_data(static::get_discussion_area($discussion), $discussiondata);
700
 
701
            // Forum discussions do not have any files associately directly with them.
702
        }
703
 
704
        $discussions->close();
705
 
706
        return $forumswithdata;
707
    }
708
 
709
    /**
710
     * Store all information about all posts that we have detected this user to have access to.
711
     *
712
     * @param   int         $userid The userid of the user whose data is to be exported.
713
     * @param   array       $mappings A list of mappings from forumid => contextid.
714
     * @return  array       Which forums had data written for them.
715
     */
716
    protected static function export_all_posts(int $userid, array $mappings) {
717
        global $DB;
718
 
719
        $commonsql = "SELECT p.discussion AS id, f.id AS forumid, d.name, d.groupid
720
                        FROM {forum} f
721
                        JOIN {forum_discussions} d ON d.forum = f.id
722
                        JOIN {forum_posts} p ON p.discussion = d.id";
723
 
724
        // All discussions with posts authored by the user or containing private replies to the user.
725
        list($foruminsql1, $forumparams1) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);
726
        $sql1 = "{$commonsql}
727
                       WHERE f.id {$foruminsql1}
728
                         AND (p.userid = :postuserid OR p.privatereplyto = :privatereplyrecipient)";
729
 
730
        // All discussions with the posts marked as read by the user.
731
        list($foruminsql2, $forumparams2) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);
732
        $sql2 = "{$commonsql}
733
                        JOIN {forum_read} fr ON fr.postid = p.id
734
                       WHERE f.id {$foruminsql2}
735
                         AND fr.userid = :readuserid";
736
 
737
        // All discussions with ratings provided by the user.
738
        list($foruminsql3, $forumparams3) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);
739
        $ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid, true);
740
        $sql3 = "{$commonsql}
741
                 {$ratingsql->join}
742
                       WHERE f.id {$foruminsql3}
743
                         AND {$ratingsql->userwhere}";
744
 
745
        $sql = "SELECT *
746
                  FROM ({$sql1} UNION {$sql2} UNION {$sql3}) united
747
              GROUP BY id, forumid, name, groupid";
748
 
749
        $params = [
750
            'postuserid' => $userid,
751
            'readuserid' => $userid,
752
            'privatereplyrecipient' => $userid,
753
        ];
754
        $params += $forumparams1;
755
        $params += $forumparams2;
756
        $params += $forumparams3;
757
        $params += $ratingsql->params;
758
 
759
        $discussions = $DB->get_records_sql($sql, $params);
760
        foreach ($discussions as $discussion) {
761
            $context = \context::instance_by_id($mappings[$discussion->forumid]);
762
            static::export_all_posts_in_discussion($userid, $context, $discussion);
763
        }
764
    }
765
 
766
    /**
767
     * Store all information about all posts that we have detected this user to have access to.
768
     *
769
     * @param   int         $userid The userid of the user whose data is to be exported.
770
     * @param   \context    $context The instance of the forum context.
771
     * @param   \stdClass   $discussion The discussion whose data is being exported.
772
     */
773
    protected static function export_all_posts_in_discussion(int $userid, \context $context, \stdClass $discussion) {
774
        global $DB, $USER;
775
 
776
        $discussionid = $discussion->id;
777
 
778
        // Find all of the posts, and post subscriptions for this forum.
779
        $ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid);
780
        $sql = "SELECT
781
                    p.*,
782
                    d.forum AS forumid,
783
                    fr.firstread,
784
                    fr.lastread,
785
                    fr.id AS readflag,
786
                    rat.id AS hasratings
787
                    FROM {forum_discussions} d
788
                    JOIN {forum_posts} p ON p.discussion = d.id
789
               LEFT JOIN {forum_read} fr ON fr.postid = p.id AND fr.userid = :readuserid
790
            {$ratingsql->join} AND {$ratingsql->userwhere}
791
                   WHERE d.id = :discussionid
792
                     AND (
793
                            p.privatereplyto = 0
794
                         OR p.privatereplyto = :privatereplyrecipient
795
                         OR p.userid = :privatereplyauthor
796
                     )
797
        ";
798
 
799
        $params = [
800
            'discussionid'  => $discussionid,
801
            'readuserid'    => $userid,
802
            'privatereplyrecipient' => $userid,
803
            'privatereplyauthor' => $userid,
804
        ];
805
        $params += $ratingsql->params;
806
 
807
        // Keep track of the forums which have data.
808
        $structure = (object) [
809
            'children' => [],
810
        ];
811
 
812
        $posts = $DB->get_records_sql($sql, $params);
813
        foreach ($posts as $post) {
814
            $post->hasdata = (isset($post->hasdata)) ? $post->hasdata : false;
815
            $post->hasdata = $post->hasdata || !empty($post->hasratings);
816
            $post->hasdata = $post->hasdata || $post->readflag;
817
            $post->hasdata = $post->hasdata || ($post->userid == $USER->id);
818
            $post->hasdata = $post->hasdata || ($post->privatereplyto == $USER->id);
819
 
820
            if (0 == $post->parent) {
821
                $structure->children[$post->id] = $post;
822
            } else {
823
                if (empty($posts[$post->parent]->children)) {
824
                    $posts[$post->parent]->children = [];
825
                }
826
                $posts[$post->parent]->children[$post->id] = $post;
827
            }
828
 
829
            // Set all parents.
830
            if ($post->hasdata) {
831
                $curpost = $post;
832
                while ($curpost->parent != 0) {
833
                    $curpost = $posts[$curpost->parent];
834
                    $curpost->hasdata = true;
835
                }
836
            }
837
        }
838
 
839
        $discussionarea = static::get_discussion_area($discussion);
840
        $discussionarea[] = get_string('posts', 'mod_forum');
841
        static::export_posts_in_structure($userid, $context, $discussionarea, $structure);
842
    }
843
 
844
    /**
845
     * Export all posts in the provided structure.
846
     *
847
     * @param   int         $userid The userid of the user whose data is to be exported.
848
     * @param   \context    $context The instance of the forum context.
849
     * @param   array       $parentarea The subcontext of the parent.
850
     * @param   \stdClass   $structure The post structure and all of its children
851
     */
852
    protected static function export_posts_in_structure(int $userid, \context $context, $parentarea, \stdClass $structure) {
853
        foreach ($structure->children as $post) {
854
            if (!$post->hasdata) {
855
                // This tree has no content belonging to the user. Skip it and all children.
856
                continue;
857
            }
858
 
859
            $postarea = array_merge($parentarea, static::get_post_area($post));
860
 
861
            // Store the post content.
862
            static::export_post_data($userid, $context, $postarea, $post);
863
 
864
            if (isset($post->children)) {
865
                // Now export children of this post.
866
                static::export_posts_in_structure($userid, $context, $postarea, $post);
867
            }
868
        }
869
    }
870
 
871
    /**
872
     * Export all data in the post.
873
     *
874
     * @param   int         $userid The userid of the user whose data is to be exported.
875
     * @param   \context    $context The instance of the forum context.
876
     * @param   array       $postarea The subcontext of the parent.
877
     * @param   \stdClass   $post The post structure and all of its children
878
     */
879
    protected static function export_post_data(int $userid, \context $context, $postarea, $post) {
880
        // Store related metadata.
881
        static::export_read_data($userid, $context, $postarea, $post);
882
 
883
        $postdata = (object) [
884
            'subject' => format_string($post->subject, true),
885
            'created' => transform::datetime($post->created),
886
            'modified' => transform::datetime($post->modified),
887
            'author_was_you' => transform::yesno($post->userid == $userid),
888
        ];
889
 
890
        if (!empty($post->privatereplyto)) {
891
            $postdata->privatereply = transform::yesno(true);
892
        }
893
 
894
        $postdata->message = writer::with_context($context)
895
            ->rewrite_pluginfile_urls($postarea, 'mod_forum', 'post', $post->id, $post->message);
896
 
897
        $postdata->message = format_text($postdata->message, $post->messageformat, (object) [
898
            'para'    => false,
899
            'trusted' => $post->messagetrust,
900
            'context' => $context,
901
        ]);
902
 
903
        writer::with_context($context)
904
            // Store the post.
905
            ->export_data($postarea, $postdata)
906
 
907
            // Store the associated files.
908
            ->export_area_files($postarea, 'mod_forum', 'post', $post->id);
909
 
910
        if ($post->userid == $userid) {
911
            // Store all ratings against this post as the post belongs to the user. All ratings on it are ratings of their content.
912
            \core_rating\privacy\provider::export_area_ratings($userid, $context, $postarea, 'mod_forum', 'post', $post->id, false);
913
 
914
            // Store all tags against this post as the tag belongs to the user.
915
            \core_tag\privacy\provider::export_item_tags($userid, $context, $postarea, 'mod_forum', 'forum_posts', $post->id);
916
 
917
            // Export all user data stored for this post from the plagiarism API.
918
            $coursecontext = $context->get_course_context();
919
            \core_plagiarism\privacy\provider::export_plagiarism_user_data($userid, $context, $postarea, [
920
                    'cmid' => $context->instanceid,
921
                    'course' => $coursecontext->instanceid,
922
                    'forum' => $post->forumid,
923
                    'discussionid' => $post->discussion,
924
                    'postid' => $post->id,
925
                ]);
926
        }
927
 
928
        // Check for any ratings that the user has made on this post.
929
        \core_rating\privacy\provider::export_area_ratings($userid,
930
                $context,
931
                $postarea,
932
                'mod_forum',
933
                'post',
934
                $post->id,
935
                $userid,
936
                true
937
            );
938
    }
939
 
940
    /**
941
     * Store data about daily digest preferences
942
     *
943
     * @param   int         $userid The userid of the user whose data is to be exported.
944
     * @param   \stdClass   $forum The forum whose data is being exported.
945
     * @param   int         $maildigest The mail digest setting for this forum.
946
     * @return  bool        Whether any data was stored.
947
     */
948
    protected static function export_digest_data(int $userid, \stdClass $forum, int $maildigest) {
949
        if (null !== $maildigest) {
950
            // The user has a specific maildigest preference for this forum.
951
            $a = (object) [
952
                'forum' => format_string($forum->name, true),
953
            ];
954
 
955
            switch ($maildigest) {
956
                case 0:
957
                    $a->type = get_string('emaildigestoffshort', 'mod_forum');
958
                    break;
959
                case 1:
960
                    $a->type = get_string('emaildigestcompleteshort', 'mod_forum');
961
                    break;
962
                case 2:
963
                    $a->type = get_string('emaildigestsubjectsshort', 'mod_forum');
964
                    break;
965
            }
966
 
967
            writer::with_context(\context_module::instance($forum->cmid))
968
                ->export_metadata([], 'digestpreference', $maildigest,
969
                    get_string('privacy:digesttypepreference', 'mod_forum', $a));
970
 
971
            return true;
972
        }
973
 
974
        return false;
975
    }
976
 
977
    /**
978
     * Store data about whether the user subscribes to forum.
979
     *
980
     * @param   int         $userid The userid of the user whose data is to be exported.
981
     * @param   \stdClass   $forum The forum whose data is being exported.
982
     * @param   int         $subscribed if the user is subscribed
983
     * @return  bool        Whether any data was stored.
984
     */
985
    protected static function export_subscription_data(int $userid, \stdClass $forum, int $subscribed) {
986
        if (null !== $subscribed) {
987
            // The user is subscribed to this forum.
988
            writer::with_context(\context_module::instance($forum->cmid))
989
                ->export_metadata([], 'subscriptionpreference', 1, get_string('privacy:subscribedtoforum', 'mod_forum'));
990
 
991
            return true;
992
        }
993
 
994
        return false;
995
    }
996
 
997
    /**
998
     * Store data about whether the user subscribes to this particular discussion.
999
     *
1000
     * @param   int         $userid The userid of the user whose data is to be exported.
1001
     * @param   \context_module $context The instance of the forum context.
1002
     * @param   \stdClass   $discussion The discussion whose data is being exported.
1003
     * @return  bool        Whether any data was stored.
1004
     */
1005
    protected static function export_discussion_subscription_data(int $userid, \context_module $context, \stdClass $discussion) {
1006
        $area = static::get_discussion_area($discussion);
1007
        if (null !== $discussion->preference) {
1008
            // The user has a specific subscription preference for this discussion.
1009
            $a = (object) [];
1010
 
1011
            switch ($discussion->preference) {
1012
                case \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED:
1013
                    $a->preference = get_string('unsubscribed', 'mod_forum');
1014
                    break;
1015
                default:
1016
                    $a->preference = get_string('subscribed', 'mod_forum');
1017
                    break;
1018
            }
1019
 
1020
            writer::with_context($context)
1021
                ->export_metadata(
1022
                    $area,
1023
                    'subscriptionpreference',
1024
                    $discussion->preference,
1025
                    get_string('privacy:discussionsubscriptionpreference', 'mod_forum', $a)
1026
                );
1027
 
1028
            return true;
1029
        }
1030
 
1031
        return true;
1032
    }
1033
 
1034
    /**
1035
     * Store forum read-tracking data about a particular forum.
1036
     *
1037
     * This is whether a forum has read-tracking enabled or not.
1038
     *
1039
     * @param   int         $userid The userid of the user whose data is to be exported.
1040
     * @param   \stdClass   $forum The forum whose data is being exported.
1041
     * @param   int         $tracke if the user is subscribed
1042
     * @return  bool        Whether any data was stored.
1043
     */
1044
    protected static function export_tracking_data(int $userid, \stdClass $forum, int $tracked) {
1045
        if (null !== $tracked) {
1046
            // The user has a main preference to track all forums, but has opted out of this one.
1047
            writer::with_context(\context_module::instance($forum->cmid))
1048
                ->export_metadata([], 'trackreadpreference', 0, get_string('privacy:readtrackingdisabled', 'mod_forum'));
1049
 
1050
            return true;
1051
        }
1052
 
1053
        return false;
1054
    }
1055
 
1056
    protected static function export_grading_data(int $userid, \stdClass $forum, int $grade) {
1057
        global $USER;
1058
        if (null !== $grade) {
1059
            $context = \context_module::instance($forum->cmid);
1060
            $exportpath = array_merge([],
1061
                [get_string('privacy:metadata:forum_grades', 'mod_forum')]);
1062
            $gradingmanager = get_grading_manager($context, 'mod_forum', 'forum');
1063
            $controller = $gradingmanager->get_active_controller();
1064
 
1065
            // Check for advanced grading and retrieve that information.
1066
            if (isset($controller)) {
1067
                $gradeduser = \core_user::get_user($userid);
1068
                // Fetch the gradeitem instance.
1069
                $gradeitem = gradeitem::instance($controller->get_component(), $context, $controller->get_area());
1070
                $grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
1071
                $controllercontext = $controller->get_context();
1072
                \core_grading\privacy\provider::export_item_data($controllercontext, $grade->id, $exportpath);
1073
            } else {
1074
                self::export_grade_data($grade, $context, $forum, $exportpath);
1075
            }
1076
            // The user has a grade for this forum.
1077
            writer::with_context(\context_module::instance($forum->cmid))
1078
                ->export_metadata($exportpath, 'gradingenabled', 1, get_string('privacy:metadata:forum_grades:grade', 'mod_forum'));
1079
 
1080
            return true;
1081
        }
1082
 
1083
        return false;
1084
    }
1085
 
1086
    protected static function export_grade_data(int $grade, \context $context, \stdClass $forum, array $path) {
1087
        $gradedata = (object)[
1088
            'forum' => $forum->name,
1089
            'grade' => $grade,
1090
        ];
1091
 
1092
        writer::with_context($context)
1093
            ->export_data($path, $gradedata);
1094
    }
1095
 
1096
    /**
1097
     * Store read-tracking information about a particular forum post.
1098
     *
1099
     * @param   int         $userid The userid of the user whose data is to be exported.
1100
     * @param   \context_module $context The instance of the forum context.
1101
     * @param   array       $postarea The subcontext for this post.
1102
     * @param   \stdClass   $post The post whose data is being exported.
1103
     * @return  bool        Whether any data was stored.
1104
     */
1105
    protected static function export_read_data(int $userid, \context_module $context, array $postarea, \stdClass $post) {
1106
        if (null !== $post->firstread) {
1107
            $a = (object) [
1108
                'firstread' => $post->firstread,
1109
                'lastread'  => $post->lastread,
1110
            ];
1111
 
1112
            writer::with_context($context)
1113
                ->export_metadata(
1114
                    $postarea,
1115
                    'postread',
1116
                    (object) [
1117
                        'firstread' => $post->firstread,
1118
                        'lastread' => $post->lastread,
1119
                    ],
1120
                    get_string('privacy:postwasread', 'mod_forum', $a)
1121
                );
1122
 
1123
            return true;
1124
        }
1125
 
1126
        return false;
1127
    }
1128
 
1129
    /**
1130
     * Delete all data for all users in the specified context.
1131
     *
1132
     * @param   context                 $context   The specific context to delete data for.
1133
     */
1134
    public static function delete_data_for_all_users_in_context(\context $context) {
1135
        global $DB;
1136
 
1137
        // Check that this is a context_module.
1138
        if (!$context instanceof \context_module) {
1139
            return;
1140
        }
1141
 
1142
        // Get the course module.
1143
        if (!$cm = get_coursemodule_from_id('forum', $context->instanceid)) {
1144
            return;
1145
        }
1146
 
1147
        $forumid = $cm->instance;
1148
 
1149
        $DB->delete_records('forum_track_prefs', ['forumid' => $forumid]);
1150
        $DB->delete_records('forum_subscriptions', ['forum' => $forumid]);
1151
        $DB->delete_records('forum_grades', ['forum' => $forumid]);
1152
        $DB->delete_records('forum_read', ['forumid' => $forumid]);
1153
        $DB->delete_records('forum_digests', ['forum' => $forumid]);
1154
 
1155
        // Delete advanced grading information.
1156
        $gradingmanager = get_grading_manager($context, 'mod_forum', 'forum');
1157
        $controller = $gradingmanager->get_active_controller();
1158
        if (isset($controller)) {
1159
            \core_grading\privacy\provider::delete_instance_data($context);
1160
        }
1161
 
1162
        $DB->delete_records('forum_grades', ['forum' => $forumid]);
1163
 
1164
        // Delete all discussion items.
1165
        $DB->delete_records_select(
1166
            'forum_queue',
1167
            "discussionid IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)",
1168
            [
1169
                'forum' => $forumid,
1170
            ]
1171
        );
1172
 
1173
        $DB->delete_records_select(
1174
            'forum_posts',
1175
            "discussion IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)",
1176
            [
1177
                'forum' => $forumid,
1178
            ]
1179
        );
1180
 
1181
        $DB->delete_records('forum_discussion_subs', ['forum' => $forumid]);
1182
        $DB->delete_records('forum_discussions', ['forum' => $forumid]);
1183
 
1184
        // Delete all files from the posts.
1185
        $fs = get_file_storage();
1186
        $fs->delete_area_files($context->id, 'mod_forum', 'post');
1187
        $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
1188
 
1189
        // Delete all ratings in the context.
1190
        \core_rating\privacy\provider::delete_ratings($context, 'mod_forum', 'post');
1191
 
1192
        // Delete all Tags.
1193
        \core_tag\privacy\provider::delete_item_tags($context, 'mod_forum', 'forum_posts');
1194
    }
1195
 
1196
    /**
1197
     * Delete all user data for the specified user, in the specified contexts.
1198
     *
1199
     * @param   approved_contextlist    $contextlist    The approved contexts and user information to delete information for.
1200
     */
1201
    public static function delete_data_for_user(approved_contextlist $contextlist) {
1202
        global $DB;
1203
        $user = $contextlist->get_user();
1204
        $userid = $user->id;
1205
        foreach ($contextlist as $context) {
1206
            // Get the course module.
1207
            $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]);
1208
            $forum = $DB->get_record('forum', ['id' => $cm->instance]);
1209
 
1210
            $DB->delete_records('forum_track_prefs', [
1211
                'forumid' => $forum->id,
1212
                'userid' => $userid,
1213
            ]);
1214
            $DB->delete_records('forum_subscriptions', [
1215
                'forum' => $forum->id,
1216
                'userid' => $userid,
1217
            ]);
1218
            $DB->delete_records('forum_read', [
1219
                'forumid' => $forum->id,
1220
                'userid' => $userid,
1221
            ]);
1222
 
1223
            $DB->delete_records('forum_digests', [
1224
                'forum' => $forum->id,
1225
                'userid' => $userid,
1226
            ]);
1227
 
1228
            // Delete all discussion items.
1229
            $DB->delete_records_select(
1230
                'forum_queue',
1231
                "userid = :userid AND discussionid IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)",
1232
                [
1233
                    'userid' => $userid,
1234
                    'forum' => $forum->id,
1235
                ]
1236
            );
1237
 
1238
            $DB->delete_records('forum_discussion_subs', [
1239
                'forum' => $forum->id,
1240
                'userid' => $userid,
1241
            ]);
1242
 
1243
            // Handle any advanced grading method data first.
1244
            $grades = $DB->get_records('forum_grades', ['forum' => $forum->id, 'userid' => $user->id]);
1245
            $gradingmanager = get_grading_manager($context, 'forum_grades', 'forum');
1246
            $controller = $gradingmanager->get_active_controller();
1247
            foreach ($grades as $grade) {
1248
                // Delete advanced grading information.
1249
                if (isset($controller)) {
1250
                    \core_grading\privacy\provider::delete_instance_data($context, $grade->id);
1251
                }
1252
            }
1253
            // Advanced grading methods have been cleared, lets clear our module now.
1254
            $DB->delete_records('forum_grades', [
1255
                'forum' => $forum->id,
1256
                'userid' => $userid,
1257
            ]);
1258
 
1259
            // Do not delete discussion or forum posts.
1260
            // Instead update them to reflect that the content has been deleted.
1261
            $postsql = "userid = :userid AND discussion IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)";
1262
            $postidsql = "SELECT fp.id FROM {forum_posts} fp WHERE {$postsql}";
1263
            $postparams = [
1264
                'forum' => $forum->id,
1265
                'userid' => $userid,
1266
            ];
1267
 
1268
            // Update the subject.
1269
            $DB->set_field_select('forum_posts', 'subject', '', $postsql, $postparams);
1270
 
1271
            // Update the message and its format.
1272
            $DB->set_field_select('forum_posts', 'message', '', $postsql, $postparams);
1273
            $DB->set_field_select('forum_posts', 'messageformat', FORMAT_PLAIN, $postsql, $postparams);
1274
 
1275
            // Mark the post as deleted.
1276
            $DB->set_field_select('forum_posts', 'deleted', 1, $postsql, $postparams);
1277
 
1278
            // Note: Do _not_ delete ratings of other users. Only delete ratings on the users own posts.
1279
            // Ratings are aggregate fields and deleting the rating of this post will have an effect on the rating
1280
            // of any post.
1281
            \core_rating\privacy\provider::delete_ratings_select($context, 'mod_forum', 'post',
1282
                    "IN ($postidsql)", $postparams);
1283
 
1284
            // Delete all Tags.
1285
            \core_tag\privacy\provider::delete_item_tags_select($context, 'mod_forum', 'forum_posts',
1286
                    "IN ($postidsql)", $postparams);
1287
 
1288
            // Delete all files from the posts.
1289
            $fs = get_file_storage();
1290
            $fs->delete_area_files_select($context->id, 'mod_forum', 'post', "IN ($postidsql)", $postparams);
1291
            $fs->delete_area_files_select($context->id, 'mod_forum', 'attachment', "IN ($postidsql)", $postparams);
1292
        }
1293
    }
1294
 
1295
    /**
1296
     * Delete multiple users within a single context.
1297
     *
1298
     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
1299
     */
1300
    public static function delete_data_for_users(approved_userlist $userlist) {
1301
        global $DB;
1302
 
1303
        $context = $userlist->get_context();
1304
        $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]);
1305
        $forum = $DB->get_record('forum', ['id' => $cm->instance]);
1306
 
1307
        list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
1308
        $params = array_merge(['forumid' => $forum->id], $userinparams);
1309
 
1310
        $DB->delete_records_select('forum_track_prefs', "forumid = :forumid AND userid {$userinsql}", $params);
1311
        $DB->delete_records_select('forum_subscriptions', "forum = :forumid AND userid {$userinsql}", $params);
1312
        $DB->delete_records_select('forum_read', "forumid = :forumid AND userid {$userinsql}", $params);
1313
        $DB->delete_records_select(
1314
            'forum_queue',
1315
            "userid {$userinsql} AND discussionid IN (SELECT id FROM {forum_discussions} WHERE forum = :forumid)",
1316
            $params
1317
        );
1318
        $DB->delete_records_select('forum_discussion_subs', "forum = :forumid AND userid {$userinsql}", $params);
1319
 
1320
        // Do not delete discussion or forum posts.
1321
        // Instead update them to reflect that the content has been deleted.
1322
        $postsql = "userid {$userinsql} AND discussion IN (SELECT id FROM {forum_discussions} WHERE forum = :forumid)";
1323
        $postidsql = "SELECT fp.id FROM {forum_posts} fp WHERE {$postsql}";
1324
 
1325
        // Update the subject.
1326
        $DB->set_field_select('forum_posts', 'subject', '', $postsql, $params);
1327
 
1328
        // Update the subject and its format.
1329
        $DB->set_field_select('forum_posts', 'message', '', $postsql, $params);
1330
        $DB->set_field_select('forum_posts', 'messageformat', FORMAT_PLAIN, $postsql, $params);
1331
 
1332
        // Mark the post as deleted.
1333
        $DB->set_field_select('forum_posts', 'deleted', 1, $postsql, $params);
1334
 
1335
        // Note: Do _not_ delete ratings of other users. Only delete ratings on the users own posts.
1336
        // Ratings are aggregate fields and deleting the rating of this post will have an effect on the rating
1337
        // of any post.
1338
        \core_rating\privacy\provider::delete_ratings_select($context, 'mod_forum', 'post', "IN ($postidsql)", $params);
1339
 
1340
        // Delete all Tags.
1341
        \core_tag\privacy\provider::delete_item_tags_select($context, 'mod_forum', 'forum_posts', "IN ($postidsql)", $params);
1342
 
1343
        // Delete all files from the posts.
1344
        $fs = get_file_storage();
1345
        $fs->delete_area_files_select($context->id, 'mod_forum', 'post', "IN ($postidsql)", $params);
1346
        $fs->delete_area_files_select($context->id, 'mod_forum', 'attachment', "IN ($postidsql)", $params);
1347
 
1348
        list($sql, $params) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
1349
        $params['forum'] = $forum->id;
1350
        // Delete advanced grading information.
1351
        $grades = $DB->get_records_select('forum_grades', "forum = :forum AND userid $sql", $params);
1352
        $gradeids = array_keys($grades);
1353
        $gradingmanager = get_grading_manager($context, 'mod_forum', 'forum');
1354
        $controller = $gradingmanager->get_active_controller();
1355
        if (isset($controller)) {
1356
            // Careful here, if no gradeids are provided then all data is deleted for the context.
1357
            if (!empty($gradeids)) {
1358
                \core_grading\privacy\provider::delete_data_for_instances($context, $gradeids);
1359
            }
1360
        }
1361
    }
1362
}