Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   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  }