1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Data provider.
19 *
20 * @package    mod_wiki
21 * @copyright  2018 Marina Glancy
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace mod_wiki\privacy;
26
27use core_privacy\local\metadata\collection;
28use core_privacy\local\request\approved_contextlist;
29use core_privacy\local\request\approved_userlist;
30use core_privacy\local\request\contextlist;
31use context_user;
32use context;
33use core_privacy\local\request\helper;
34use core_privacy\local\request\transform;
35use core_privacy\local\request\userlist;
36use core_privacy\local\request\writer;
37
38defined('MOODLE_INTERNAL') || die();
39
40/**
41 * Data provider class.
42 *
43 * @package    mod_wiki
44 * @copyright  2018 Marina Glancy
45 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46 */
47class provider implements
48    \core_privacy\local\metadata\provider,
49    \core_privacy\local\request\core_userlist_provider,
50    \core_privacy\local\request\plugin\provider {
51
52    /**
53     * Returns metadata.
54     *
55     * @param collection $collection The initialised collection to add items to.
56     * @return collection A listing of user data stored through this system.
57     */
58    public static function get_metadata(collection $collection) : collection {
59
60        $collection->add_database_table('wiki_subwikis', [
61            'userid' => 'privacy:metadata:wiki_subwikis:userid',
62            'groupid' => 'privacy:metadata:wiki_subwikis:groupid',
63        ], 'privacy:metadata:wiki_subwikis');
64
65        $collection->add_database_table('wiki_pages', [
66            'userid' => 'privacy:metadata:wiki_pages:userid',
67            'title' => 'privacy:metadata:wiki_pages:title',
68            'cachedcontent' => 'privacy:metadata:wiki_pages:cachedcontent',
69            'timecreated' => 'privacy:metadata:wiki_pages:timecreated',
70            'timemodified' => 'privacy:metadata:wiki_pages:timemodified',
71            'timerendered' => 'privacy:metadata:wiki_pages:timerendered',
72            'pageviews' => 'privacy:metadata:wiki_pages:pageviews',
73            'readonly' => 'privacy:metadata:wiki_pages:readonly',
74        ], 'privacy:metadata:wiki_pages');
75
76        $collection->add_database_table('wiki_versions', [
77            'userid' => 'privacy:metadata:wiki_versions:userid',
78            'content' => 'privacy:metadata:wiki_versions:content',
79            'contentformat' => 'privacy:metadata:wiki_versions:contentformat',
80            'version' => 'privacy:metadata:wiki_versions:version',
81            'timecreated' => 'privacy:metadata:wiki_versions:timecreated',
82        ], 'privacy:metadata:wiki_versions');
83
84        $collection->add_database_table('wiki_locks', [
85            'userid' => 'privacy:metadata:wiki_locks:userid',
86            'sectionname' => 'privacy:metadata:wiki_locks:sectionname',
87            'lockedat' => 'privacy:metadata:wiki_locks:lockedat',
88        ], 'privacy:metadata:wiki_locks');
89
90        $collection->link_subsystem('core_files', 'privacy:metadata:core_files');
91        $collection->link_subsystem('core_tag', 'privacy:metadata:core_tag');
92        $collection->link_subsystem('core_comment', 'privacy:metadata:core_comment');
93
94        // We do not report on wiki, wiki_synonyms, wiki_links because this is just context-related data.
95
96        return $collection;
97    }
98
99    /**
100     * Get the list of contexts that contain user information for the specified user.
101     *
102     * @param int $userid The user to search.
103     * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
104     */
105    public static function get_contexts_for_userid(int $userid) : contextlist {
106        $contextlist = new contextlist();
107
108        $contextlist->add_from_sql('SELECT ctx.id
109            FROM {modules} m
110            JOIN {course_modules} cm ON cm.module = m.id AND m.name = :modname
111            JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
112            JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
113            LEFT JOIN {wiki_pages} p ON p.subwikiid = s.id
114            LEFT JOIN {wiki_versions} v ON v.pageid = p.id AND v.userid = :userid3
115            LEFT JOIN {wiki_locks} l ON l.pageid = p.id AND l.userid = :userid4
116            LEFT JOIN {comments} com ON com.itemid = p.id AND com.commentarea = :commentarea
117                AND com.contextid = ctx.id AND com.userid = :userid5
118            WHERE s.userid = :userid1 OR p.userid = :userid2 OR v.id IS NOT NULL OR l.id IS NOT NULL OR com.id IS NOT NULL',
119            ['modname' => 'wiki', 'contextlevel' => CONTEXT_MODULE, 'userid1' => $userid, 'userid2' => $userid,
120                'userid3' => $userid, 'userid4' => $userid, 'commentarea' => 'wiki_page', 'userid5' => $userid]);
121
122        return $contextlist;
123    }
124
125    /**
126     * Get the list of users who have data within a context.
127     *
128     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
129     */
130    public static function get_users_in_context(userlist $userlist) {
131        $context = $userlist->get_context();
132
133        if (!is_a($context, \context_module::class)) {
134            return;
135        }
136
137        $params = [
138            'modname' => 'wiki',
139            'contextlevel' => CONTEXT_MODULE,
140            'contextid' => $context->id,
141        ];
142
143        $sql = "
144          SELECT s.userid
145            FROM {modules} m
146            JOIN {course_modules} cm ON cm.module = m.id AND m.name = :modname
147            JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
148            JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
149            WHERE ctx.id = :contextid";
150
151        $userlist->add_from_sql('userid', $sql, $params);
152
153        $sql = "
154          SELECT p.userid
155            FROM {modules} m
156            JOIN {course_modules} cm ON cm.module = m.id AND m.name = :modname
157            JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
158            JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
159            JOIN {wiki_pages} p ON p.subwikiid = s.id
160            WHERE ctx.id = :contextid";
161
162        $userlist->add_from_sql('userid', $sql, $params);
163
164        $sql = "
165          SELECT v.userid
166            FROM {modules} m
167            JOIN {course_modules} cm ON cm.module = m.id AND m.name = :modname
168            JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
169            JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
170            JOIN {wiki_pages} p ON p.subwikiid = s.id
171            JOIN {wiki_versions} v ON v.pageid = p.id
172            WHERE ctx.id = :contextid";
173
174        $userlist->add_from_sql('userid', $sql, $params);
175
176        $sql = "
177          SELECT l.userid
178            FROM {modules} m
179            JOIN {course_modules} cm ON cm.module = m.id AND m.name = :modname
180            JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
181            JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
182            JOIN {wiki_pages} p ON p.subwikiid = s.id
183            JOIN {wiki_locks} l ON l.pageid = p.id
184            WHERE ctx.id = :contextid";
185
186        $userlist->add_from_sql('userid', $sql, $params);
187        \core_comment\privacy\provider::get_users_in_context_from_sql($userlist, 'com', 'mod_wiki', 'wiki_page', $context->id);
188    }
189
190    /**
191     * Add one subwiki to the export
192     *
193     * Each page is added as related data because all pages in one subwiki share the same filearea
194     *
195     * @param stdClass $user
196     * @param context $context
197     * @param array $subwiki
198     * @param string $wikimode
199     */
200    protected static function export_subwiki($user, context $context, $subwiki, $wikimode) {
201        if (empty($subwiki)) {
202            return;
203        }
204        $subwikiid = key($subwiki);
205        $pages = $subwiki[$subwikiid]['pages'];
206        unset($subwiki[$subwikiid]['pages']);
207        writer::with_context($context)->export_data([$subwikiid], (object)$subwiki[$subwikiid]);
208        $allfiles = $wikimode === 'individual'; // Whether to export all files or only the ones that are used.
209
210        $alltexts = ''; // Store all texts that reference files to search which files are used.
211        foreach ($pages as $page => $entry) {
212            // Preprocess current page contents.
213            if (!$allfiles && self::text_has_files($entry['page']['cachedcontent'])) {
214                $alltexts .= $entry['page']['cachedcontent'];
215            }
216            $entry['page']['cachedcontent'] = format_text(writer::with_context($context)
217                ->rewrite_pluginfile_urls([$subwikiid], 'mod_wiki', 'attachments',
218                    $subwikiid, $entry['page']['cachedcontent']), FORMAT_HTML, ['context' => $context]);
219            // Add page tags.
220            $pagetags = \core_tag_tag::get_item_tags_array('mod_wiki', 'page', $entry['page']['id']);
221            if ($pagetags) {
222                $entry['page']['tags'] = $pagetags;
223            }
224
225            // Preprocess revisions.
226            if (!empty($entry['revisions'])) {
227                // For each revision this user has made preprocess the contents.
228                foreach ($entry['revisions'] as &$revision) {
229                    if ((!$allfiles && self::text_has_files($revision['content']))) {
230                        $alltexts .= $revision['content'];
231                    }
232                    $revision['content'] = writer::with_context($context)
233                        ->rewrite_pluginfile_urls([$subwikiid], 'mod_wiki', 'attachments', $subwikiid, $revision['content']);
234                }
235            }
236            $comments = self::get_page_comments($user, $context, $entry['page']['id'], !array_key_exists('userid', $entry['page']));
237            if ($comments) {
238                $entry['page']['comments'] = $comments;
239            }
240            writer::with_context($context)->export_related_data([$subwikiid], $page, $entry);
241        }
242
243        if ($allfiles) {
244            // Export all files.
245            writer::with_context($context)->export_area_files([$subwikiid], 'mod_wiki', 'attachments', $subwikiid);
246        } else {
247            // Analyze which files are used in the texts.
248            self::export_used_files($context, $subwikiid, $alltexts);
249        }
250    }
251
252    /**
253     * Retrieves page comments
254     *
255     * We can not use \core_comment\privacy\provider::export_comments() because it expects each item to have a separate
256     * subcontext and we store wiki pages as related data to subwiki because the files are shared between pages.
257     *
258     * @param stdClass $user
259     * @param \context $context
260     * @param int $pageid
261     * @param bool $onlyforthisuser
262     * @return array
263     */
264    protected static function get_page_comments($user, \context $context, $pageid, $onlyforthisuser = true) {
265        global $USER, $DB;
266        $params = [
267            'contextid' => $context->id,
268            'commentarea' => 'wiki_page',
269            'itemid' => $pageid
270        ];
271        $sql = "SELECT c.id, c.content, c.format, c.timecreated, c.userid
272                  FROM {comments} c
273                 WHERE c.contextid = :contextid AND
274                       c.commentarea = :commentarea AND
275                       c.itemid = :itemid";
276        if ($onlyforthisuser) {
277            $sql .= " AND c.userid = :userid";
278            $params['userid'] = $USER->id;
279        }
280        $sql .= " ORDER BY c.timecreated DESC";
281
282        $rs = $DB->get_recordset_sql($sql, $params);
283        $comments = [];
284        foreach ($rs as $record) {
285            if ($record->userid != $user->id) {
286                // Clean HTML in comments that were added by other users.
287                $comment = ['content' => format_text($record->content, $record->format, ['context' => $context])];
288            } else {
289                // Export comments made by this user as they are stored.
290                $comment = ['content' => $record->content, 'contentformat' => $record->format];
291            }
292            $comment += [
293                'time' => transform::datetime($record->timecreated),
294                'userid' => transform::user($record->userid),
295            ];
296            $comments[] = (object)$comment;
297        }
298        $rs->close();
299        return $comments;
300    }
301
302    /**
303     * Check if text has embedded files
304     *
305     * @param string $str
306     * @return bool
307     */
308    protected static function text_has_files($str) {
309        return strpos($str, '@@PLUGINFILE@@') !== false;
310    }
311
312    /**
313     * Analyze which files are used in the texts and export
314     * @param context $context
315     * @param int $subwikiid
316     * @param string $alltexts
317     * @return int|void
318     */
319    protected static function export_used_files($context, $subwikiid, $alltexts) {
320        if (!self::text_has_files($alltexts)) {
321            return;
322        }
323        $fs = get_file_storage();
324        $files = $fs->get_area_files($context->id, 'mod_wiki', 'attachments', $subwikiid,
325            'filepath, filename', false);
326        if (empty($files)) {
327            return;
328        }
329        usort($files, function($file1, $file2) {
330            return strcmp($file2->get_filepath(), $file1->get_filename());
331        });
332        foreach ($files as $file) {
333            $filepath = $file->get_filepath() . $file->get_filename();
334            $needles = ['@@PLUGINFILE@@' . s($filepath),
335                '@@PLUGINFILE@@' . $filepath,
336                '@@PLUGINFILE@@' . str_replace(' ', '%20', $filepath),
337                '@@PLUGINFILE@@' . s($filepath),
338                '@@PLUGINFILE@@' . s(str_replace(' ', '%20', $filepath))
339            ];
340            $needles = array_unique($needles);
341            $newtext = str_replace($needles, '', $alltexts);
342            if ($newtext !== $alltexts) {
343                $alltexts = $newtext;
344                writer::with_context($context)->export_file([$subwikiid], $file);
345                if (!self::text_has_files($alltexts)) {
346                    return;
347                }
348            }
349        }
350    }
351
352    /**
353     * Export all user data for the specified user, in the specified contexts.
354     *
355     * @param approved_contextlist $contextlist The approved contexts to export information for.
356     */
357    public static function export_user_data(approved_contextlist $contextlist) {
358        global $DB;
359
360        foreach ($contextlist as $context) {
361            if ($context->contextlevel != CONTEXT_MODULE) {
362                continue;
363            }
364            $user = $contextlist->get_user();
365
366            $rs = $DB->get_recordset_sql('SELECT w.wikimode, s.id AS subwikiid,
367                    s.groupid AS subwikigroupid, s.userid AS subwikiuserid,
368                    p.id AS pageid, p.userid AS pageuserid, p.title, p.cachedcontent, p.timecreated AS pagetimecreated,
369                    p.timemodified AS pagetimemodified, p.timerendered AS pagetimerendered, p.pageviews, p.readonly,
370                    v.id AS versionid, v.content, v.contentformat, v.version, v.timecreated AS versiontimecreated,
371                    l.id AS lockid, l.sectionname, l.lockedat
372                FROM {course_modules} cm
373                JOIN {wiki} w ON w.id = cm.instance
374                JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
375                LEFT JOIN {wiki_pages} p ON p.subwikiid = s.id
376                LEFT JOIN {wiki_versions} v ON v.pageid = p.id AND v.userid = :user4
377                LEFT JOIN {wiki_locks} l ON l.pageid = p.id AND l.userid = :user5
378                WHERE cm.id = :cmid AND (s.userid = :user1 OR p.userid = :user2 OR v.userid = :user3 OR l.userid = :user6 OR
379                     EXISTS (SELECT 1 FROM {comments} com WHERE com.itemid = p.id AND com.commentarea = :commentarea
380                          AND com.contextid = :ctxid AND com.userid = :user7)
381                )
382                ORDER BY s.id, p.id, v.id',
383                ['cmid' => $context->instanceid,
384                    'user1' => $user->id, 'user2' => $user->id, 'user3' => $user->id, 'user4' => $user->id,
385                    'user5' => $user->id, 'user6' => $user->id, 'user7' => $user->id, 'commentarea' => 'wiki_page',
386                    'ctxid' => $context->id]);
387
388            if (!$rs->current()) {
389                $rs->close();
390                continue;
391            }
392
393            $subwiki = [];
394            $wikimode = null;
395            foreach ($rs as $record) {
396                if ($wikimode === null) {
397                    $wikimode = $record->wikimode;
398                }
399                if (!isset($subwiki[$record->subwikiid])) {
400                    self::export_subwiki($user, $context, $subwiki, $wikimode);
401                    $subwiki = [$record->subwikiid => [
402                        'groupid' => $record->subwikigroupid,
403                        'userid' => $record->subwikiuserid ? transform::user($record->subwikiuserid) : 0,
404                        'pages' => []
405                    ]];
406                }
407
408                if (!$record->pageid) {
409                    // This is an empty individual wiki.
410                    continue;
411                }
412
413                // Prepend page title with the page id to guarantee uniqueness.
414                $pagetitle = format_string($record->title, true, ['context' => $context]);
415                $page = $record->pageid . ' ' . $pagetitle;
416                if (!isset($subwiki[$record->subwikiid]['pages'][$page])) {
417                    // Export basic details about the page.
418                    $subwiki[$record->subwikiid]['pages'][$page] = ['page' => [
419                        'id' => $record->pageid,
420                        'title' => $pagetitle,
421                        'cachedcontent' => $record->cachedcontent,
422                    ]];
423                    if ($record->pageuserid == $user->id) {
424                        // This page belongs to this user. Export all details.
425                        $subwiki[$record->subwikiid]['pages'][$page]['page'] += [
426                            'userid' => transform::user($user->id),
427                            'timecreated' => transform::datetime($record->pagetimecreated),
428                            'timemodified' => transform::datetime($record->pagetimemodified),
429                            'timerendered' => transform::datetime($record->pagetimerendered),
430                            'pageviews' => $record->pageviews,
431                            'readonly' => $record->readonly,
432                        ];
433
434                        $subwiki[$record->subwikiid]['pages'][$page]['page']['userid'] = transform::user($user->id);
435                    }
436                }
437
438                if ($record->versionid) {
439                    $subwiki[$record->subwikiid]['pages'][$page]['revisions'][$record->versionid] = [
440                        'content' => $record->content,
441                        'contentformat' => $record->contentformat,
442                        'version' => $record->version,
443                        'timecreated' => transform::datetime($record->versiontimecreated)
444                    ];
445                }
446
447                if ($record->lockid) {
448                    $subwiki[$record->subwikiid]['pages'][$page]['locks'][$record->lockid] = [
449                        'sectionname' => $record->sectionname,
450                        'lockedat' => transform::datetime($record->lockedat),
451                    ];
452                }
453
454            }
455            self::export_subwiki($user, $context, $subwiki, $wikimode);
456
457            if ($subwiki) {
458                // Export wiki itself.
459                $contextdata = helper::get_context_data($context, $user);
460                helper::export_context_files($context, $user);
461                writer::with_context($context)->export_data([], $contextdata);
462            }
463
464            $rs->close();
465        }
466    }
467
468    /**
469     * Delete all data for all users in the specified context.
470     *
471     * @param context $context The specific context to delete data for.
472     */
473    public static function delete_data_for_all_users_in_context(context $context) {
474        global $DB;
475
476        if ($context->contextlevel != CONTEXT_MODULE) {
477            return;
478        }
479
480        $subwikis = $DB->get_fieldset_sql('SELECT s.id
481              FROM {course_modules} cm
482              JOIN {modules} m ON m.name = :wiki AND cm.module = m.id
483              JOIN {wiki_subwikis} s ON s.wikiid = cm.instance
484             WHERE cm.id = :cmid',
485            ['cmid' => $context->instanceid, 'wiki' => 'wiki']);
486        if (!$subwikis) {
487            return;
488        }
489
490        $fs = get_file_storage();
491        $fs->delete_area_files($context->id, 'mod_wiki', 'attachments');
492
493        \core_tag\privacy\provider::delete_item_tags($context, 'mod_wiki', 'page');
494
495        \core_comment\privacy\provider::delete_comments_for_all_users($context, 'mod_wiki', 'wiki_page');
496
497        list($sql, $params) = $DB->get_in_or_equal($subwikis);
498        $DB->delete_records_select('wiki_locks', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid '.$sql.')', $params);
499        $DB->delete_records_select('wiki_versions', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid '.$sql.')', $params);
500        $DB->delete_records_select('wiki_synonyms', 'subwikiid '.$sql, $params);
501        $DB->delete_records_select('wiki_links', 'subwikiid '.$sql, $params);
502        $DB->delete_records_select('wiki_pages', 'subwikiid '.$sql, $params);
503        $DB->delete_records_select('wiki_subwikis', 'id '.$sql, $params);
504
505        $DB->delete_records('tag_instance', ['contextid' => $context->id, 'component' => 'mod_wiki', 'itemtype' => 'page']);
506    }
507
508    /**
509     * Delete all user data for the specified user, in the specified contexts.
510     *
511     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
512     */
513    public static function delete_data_for_user(approved_contextlist $contextlist) {
514        global $DB;
515
516        $contextids = $contextlist->get_contextids();
517
518        if (!$contextids) {
519            return;
520        }
521
522        // Remove only individual subwikis. Contributions to collaborative wikis is not considered personal contents.
523        list($ctxsql, $ctxparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
524        $subwikis = $DB->get_records_sql_menu('SELECT s.id, ctx.id AS ctxid
525              FROM {context} ctx
526              JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmod
527              JOIN {modules} m ON m.name = :wiki AND cm.module = m.id
528              JOIN {wiki_subwikis} s ON s.wikiid = cm.instance AND s.userid = :userid
529             WHERE ctx.id ' . $ctxsql,
530            ['userid' => (int)$contextlist->get_user()->id, 'wiki' => 'wiki', 'contextmod' => CONTEXT_MODULE] + $ctxparams);
531
532        if ($subwikis) {
533            // We found individual subwikis that need to be deleted completely.
534
535            $fs = get_file_storage();
536            foreach ($subwikis as $subwikiid => $contextid) {
537                $fs->delete_area_files($contextid, 'mod_wiki', 'attachments', $subwikiid);
538                \core_comment\privacy\provider::delete_comments_for_all_users_select(context::instance_by_id($contextid),
539                    'mod_wiki', 'wiki_page', "IN (SELECT id FROM {wiki_pages} WHERE subwikiid=:subwikiid)",
540                    ['subwikiid' => $subwikiid]);
541            }
542
543            list($sql, $params) = $DB->get_in_or_equal(array_keys($subwikis), SQL_PARAMS_NAMED);
544
545            $DB->execute("DELETE FROM {tag_instance} WHERE component=:component AND itemtype=:itemtype AND itemid IN
546                (SELECT id FROM {wiki_pages} WHERE subwikiid $sql)",
547                ['component' => 'mod_wiki', 'itemtype' => 'page'] + $params);
548
549            $DB->delete_records_select('wiki_locks', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid ' . $sql . ')',
550                $params);
551            $DB->delete_records_select('wiki_versions', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid ' . $sql . ')',
552                $params);
553            $DB->delete_records_select('wiki_synonyms', 'subwikiid ' . $sql, $params);
554            $DB->delete_records_select('wiki_links', 'subwikiid ' . $sql, $params);
555            $DB->delete_records_select('wiki_pages', 'subwikiid ' . $sql, $params);
556            $DB->delete_records_select('wiki_subwikis', 'id ' . $sql, $params);
557        }
558
559        // Remove comments made by this user on all other wiki pages.
560        \core_comment\privacy\provider::delete_comments_for_user($contextlist, 'mod_wiki', 'wiki_page');
561    }
562
563    /**
564     * Delete multiple users within a single context.
565     *
566     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
567     */
568    public static function delete_data_for_users(approved_userlist $userlist) {
569        global $DB;
570        $context = $userlist->get_context();
571        $userids = $userlist->get_userids();
572
573        if ($context->contextlevel != CONTEXT_MODULE) {
574            return;
575        }
576
577        // Remove only individual subwikis. Contributions to collaborative wikis is not considered personal contents.
578        list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
579        $params = [
580            'wiki' => 'wiki',
581            'contextmod' => CONTEXT_MODULE,
582            'contextid' => $context->id,
583        ];
584
585        $params = array_merge($inparams, $params);
586        $sql = "SELECT s.id
587                  FROM {context} ctx
588                  JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmod
589                  JOIN {modules} m ON m.name = :wiki AND cm.module = m.id
590                  JOIN {wiki_subwikis} s ON s.wikiid = cm.instance
591                 WHERE ctx.id = :contextid
592                   AND s.userid {$insql}";
593
594        $subwikis = $DB->get_fieldset_sql($sql, $params);
595
596        if ($subwikis) {
597            // We found individual subwikis that need to be deleted completely.
598
599            $fs = get_file_storage();
600            foreach ($subwikis as $subwikiid) {
601                $fs->delete_area_files($context->id, 'mod_wiki', 'attachments', $subwikiid);
602                \core_comment\privacy\provider::delete_comments_for_all_users_select(context::instance_by_id($context->id),
603                    'mod_wiki', 'wiki_page', "IN (SELECT id FROM {wiki_pages} WHERE subwikiid=:subwikiid)",
604                    ['subwikiid' => $subwikiid]);
605            }
606
607            list($insql, $inparams) = $DB->get_in_or_equal($subwikis, SQL_PARAMS_NAMED);
608            $params = ['component' => 'mod_wiki', 'itemtype' => 'page'];
609            $params = array_merge($inparams, $params);
610            $sql = "DELETE FROM {tag_instance}
611                          WHERE component=:component
612                            AND itemtype=:itemtype
613                            AND itemid IN
614                                (SELECT id
615                                FROM {wiki_pages}
616                                WHERE subwikiid $insql)";
617
618            $DB->execute($sql, $params);
619
620            $DB->delete_records_select('wiki_locks', "pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid {$insql})", $params);
621            $DB->delete_records_select('wiki_versions', "pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid {$insql})",
622                    $params);
623            $DB->delete_records_select('wiki_synonyms', "subwikiid {$insql}", $params);
624            $DB->delete_records_select('wiki_links', "subwikiid {$insql}", $params);
625            $DB->delete_records_select('wiki_pages', "subwikiid {$insql}", $params);
626            $DB->delete_records_select('wiki_subwikis', "id {$insql}", $params);
627        }
628
629        // Remove comments made by this user on all other wiki pages.
630        \core_comment\privacy\provider::delete_comments_for_users($userlist, 'mod_wiki', 'wiki_page');
631    }
632}
633