Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
   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  
  25  namespace mod_wiki\privacy;
  26  
  27  use core_privacy\local\metadata\collection;
  28  use core_privacy\local\request\approved_contextlist;
  29  use core_privacy\local\request\approved_userlist;
  30  use core_privacy\local\request\contextlist;
  31  use context_user;
  32  use context;
  33  use core_privacy\local\request\helper;
  34  use core_privacy\local\request\transform;
  35  use core_privacy\local\request\userlist;
  36  use core_privacy\local\request\writer;
  37  
  38  defined('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   */
  47  class 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  }