Differences Between: [Versions 311 and 402] [Versions 311 and 403]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body