AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Privacy Subsystem implementation for mod_forum.** @package mod_forum* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/namespace mod_forum\privacy;use core_grades\component_gradeitem as gradeitem;use \core_privacy\local\request\userlist;use \core_privacy\local\request\approved_contextlist;use \core_privacy\local\request\approved_userlist;use \core_privacy\local\request\deletion_criteria;use \core_privacy\local\request\writer;use \core_privacy\local\request\helper as request_helper;use \core_privacy\local\metadata\collection;use \core_privacy\local\request\transform;use tool_dataprivacy\context_instance;defined('MOODLE_INTERNAL') || die();require_once($CFG->dirroot . '/grade/grading/lib.php');/*** Implementation of the privacy subsystem plugin provider for the forum activity module.** @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class provider implements// This plugin has data.\core_privacy\local\metadata\provider,// This plugin currently implements the original plugin\provider interface.\core_privacy\local\request\plugin\provider,// This plugin is capable of determining which users have data within it.\core_privacy\local\request\core_userlist_provider,// This plugin has some sitewide user preferences to export.\core_privacy\local\request\user_preference_provider{use subcontext_info;/*** Returns meta data about this system.** @param collection $items The initialised collection to add items to.* @return collection A listing of user data stored through this system.*/public static function get_metadata(collection $items): collection {// The 'forum' table does not store any specific user data.$items->add_database_table('forum_digests', ['forum' => 'privacy:metadata:forum_digests:forum','userid' => 'privacy:metadata:forum_digests:userid','maildigest' => 'privacy:metadata:forum_digests:maildigest',], 'privacy:metadata:forum_digests');// The 'forum_discussions' table stores the metadata about each forum discussion.$items->add_database_table('forum_discussions', ['name' => 'privacy:metadata:forum_discussions:name','userid' => 'privacy:metadata:forum_discussions:userid','assessed' => 'privacy:metadata:forum_discussions:assessed','timemodified' => 'privacy:metadata:forum_discussions:timemodified','usermodified' => 'privacy:metadata:forum_discussions:usermodified',], 'privacy:metadata:forum_discussions');// The 'forum_discussion_subs' table stores information about which discussions a user is subscribed to.$items->add_database_table('forum_discussion_subs', ['discussionid' => 'privacy:metadata:forum_discussion_subs:discussionid','preference' => 'privacy:metadata:forum_discussion_subs:preference','userid' => 'privacy:metadata:forum_discussion_subs:userid',], 'privacy:metadata:forum_discussion_subs');// The 'forum_posts' table stores the metadata about each forum discussion.$items->add_database_table('forum_posts', ['discussion' => 'privacy:metadata:forum_posts:discussion','parent' => 'privacy:metadata:forum_posts:parent','created' => 'privacy:metadata:forum_posts:created','modified' => 'privacy:metadata:forum_posts:modified','subject' => 'privacy:metadata:forum_posts:subject','message' => 'privacy:metadata:forum_posts:message','userid' => 'privacy:metadata:forum_posts:userid','privatereplyto' => 'privacy:metadata:forum_posts:privatereplyto',], 'privacy:metadata:forum_posts');// The 'forum_queue' table contains user data, but it is only a temporary cache of other data.// We should not need to export it as it does not allow profiling of a user.// The 'forum_read' table stores data about which forum posts have been read by each user.$items->add_database_table('forum_read', ['userid' => 'privacy:metadata:forum_read:userid','discussionid' => 'privacy:metadata:forum_read:discussionid','postid' => 'privacy:metadata:forum_read:postid','firstread' => 'privacy:metadata:forum_read:firstread','lastread' => 'privacy:metadata:forum_read:lastread',], 'privacy:metadata:forum_read');// The 'forum_subscriptions' table stores information about which forums a user is subscribed to.$items->add_database_table('forum_subscriptions', ['userid' => 'privacy:metadata:forum_subscriptions:userid','forum' => 'privacy:metadata:forum_subscriptions:forum',], 'privacy:metadata:forum_subscriptions');// The 'forum_subscriptions' table stores information about which forums a user is subscribed to.$items->add_database_table('forum_track_prefs', ['userid' => 'privacy:metadata:forum_track_prefs:userid','forumid' => 'privacy:metadata:forum_track_prefs:forumid',], 'privacy:metadata:forum_track_prefs');// The 'forum_queue' table stores temporary data that is not exported/deleted.$items->add_database_table('forum_queue', ['userid' => 'privacy:metadata:forum_queue:userid','discussionid' => 'privacy:metadata:forum_queue:discussionid','postid' => 'privacy:metadata:forum_queue:postid','timemodified' => 'privacy:metadata:forum_queue:timemodified'], 'privacy:metadata:forum_queue');// The 'forum_grades' table stores grade data.$items->add_database_table('forum_grades', ['userid' => 'privacy:metadata:forum_grades:userid','forum' => 'privacy:metadata:forum_grades:forum','grade' => 'privacy:metadata:forum_grades:grade',], 'privacy:metadata:forum_grades');// Forum posts can be tagged and rated.$items->link_subsystem('core_tag', 'privacy:metadata:core_tag');$items->link_subsystem('core_rating', 'privacy:metadata:core_rating');// There are several user preferences.$items->add_user_preference('maildigest', 'privacy:metadata:preference:maildigest');$items->add_user_preference('autosubscribe', 'privacy:metadata:preference:autosubscribe');$items->add_user_preference('trackforums', 'privacy:metadata:preference:trackforums');$items->add_user_preference('markasreadonnotification', 'privacy:metadata:preference:markasreadonnotification');$items->add_user_preference('forum_discussionlistsortorder','privacy:metadata:preference:forum_discussionlistsortorder');return $items;}/*** Get the list of contexts that contain user information for the specified user.** In the case of forum, that is any forum where the user has made any post, rated any content, or has any preferences.** @param int $userid The user to search.* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.*/public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist {$contextlist = new \core_privacy\local\request\contextlist();$params = ['modname' => 'forum','contextlevel' => CONTEXT_MODULE,'userid' => $userid,];// Discussion creators.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussions} d ON d.forum = f.idWHERE d.userid = :userid";$contextlist->add_from_sql($sql, $params);// Post authors.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussions} d ON d.forum = f.idJOIN {forum_posts} p ON p.discussion = d.idWHERE p.userid = :userid";$contextlist->add_from_sql($sql, $params);// Forum digest records.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_digests} dig ON dig.forum = f.idWHERE dig.userid = :userid";$contextlist->add_from_sql($sql, $params);// Forum subscriptions.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_subscriptions} sub ON sub.forum = f.idWHERE sub.userid = :userid";$contextlist->add_from_sql($sql, $params);// Discussion subscriptions.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussion_subs} dsub ON dsub.forum = f.idWHERE dsub.userid = :userid";$contextlist->add_from_sql($sql, $params);// Discussion tracking preferences.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_track_prefs} pref ON pref.forumid = f.idWHERE pref.userid = :userid";$contextlist->add_from_sql($sql, $params);// Discussion read records.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_read} hasread ON hasread.forumid = f.idWHERE hasread.userid = :userid";$contextlist->add_from_sql($sql, $params);// Rating authors.$ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid, true);$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussions} d ON d.forum = f.idJOIN {forum_posts} p ON p.discussion = d.id{$ratingsql->join}WHERE {$ratingsql->userwhere}";$params += $ratingsql->params;$contextlist->add_from_sql($sql, $params);// Forum grades.$sql = "SELECT c.idFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevelJOIN {modules} m ON m.id = cm.module AND m.name = :modnameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_grades} fg ON fg.forum = f.idWHERE fg.userid = :userid";$contextlist->add_from_sql($sql, $params);return $contextlist;}/*** Get the list of users within a specific context.** @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.*/public static function get_users_in_context(userlist $userlist) {$context = $userlist->get_context();if (!is_a($context, \context_module::class)) {return;}$params = ['instanceid' => $context->instanceid,'modulename' => 'forum',];// Discussion authors.$sql = "SELECT d.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussions} d ON d.forum = f.idWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);// Forum authors.$sql = "SELECT p.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussions} d ON d.forum = f.idJOIN {forum_posts} p ON d.id = p.discussionWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);// Forum post ratings.$sql = "SELECT p.idFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussions} d ON d.forum = f.idJOIN {forum_posts} p ON d.id = p.discussionWHERE cm.id = :instanceid";\core_rating\privacy\provider::get_users_in_context_from_sql($userlist, 'rat', 'mod_forum', 'post', $sql, $params);// Forum Digest settings.$sql = "SELECT dig.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_digests} dig ON dig.forum = f.idWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);// Forum Subscriptions.$sql = "SELECT sub.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_subscriptions} sub ON sub.forum = f.idWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);// Discussion subscriptions.$sql = "SELECT dsub.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_discussion_subs} dsub ON dsub.forum = f.idWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);// Read Posts.$sql = "SELECT hasread.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_read} hasread ON hasread.forumid = f.idWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);// Tracking Preferences.$sql = "SELECT pref.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_track_prefs} pref ON pref.forumid = f.idWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);// Forum grades.$sql = "SELECT fg.useridFROM {course_modules} cmJOIN {modules} m ON m.id = cm.module AND m.name = :modulenameJOIN {forum} f ON f.id = cm.instanceJOIN {forum_grades} fg ON fg.forum = f.idWHERE cm.id = :instanceid";$userlist->add_from_sql('userid', $sql, $params);}/*** Store all user preferences for the plugin.** @param int $userid The userid of the user whose data is to be exported.*/public static function export_user_preferences(int $userid) {$user = \core_user::get_user($userid);switch ($user->maildigest) {case 1:$digestdescription = get_string('emaildigestcomplete');break;case 2:$digestdescription = get_string('emaildigestsubjects');break;case 0:default:$digestdescription = get_string('emaildigestoff');break;}writer::export_user_preference('mod_forum', 'maildigest', $user->maildigest, $digestdescription);switch ($user->autosubscribe) {case 0:$subscribedescription = get_string('autosubscribeno');break;case 1:default:$subscribedescription = get_string('autosubscribeyes');break;}writer::export_user_preference('mod_forum', 'autosubscribe', $user->autosubscribe, $subscribedescription);switch ($user->trackforums) {case 0:$trackforumdescription = get_string('trackforumsno');break;case 1:default:$trackforumdescription = get_string('trackforumsyes');break;}writer::export_user_preference('mod_forum', 'trackforums', $user->trackforums, $trackforumdescription);$markasreadonnotification = get_user_preferences('markasreadonnotification', null, $user->id);if (null !== $markasreadonnotification) {switch ($markasreadonnotification) {case 0:$markasreadonnotificationdescription = get_string('markasreadonnotificationno', 'mod_forum');break;case 1:default:$markasreadonnotificationdescription = get_string('markasreadonnotificationyes', 'mod_forum');break;}writer::export_user_preference('mod_forum', 'markasreadonnotification', $markasreadonnotification,$markasreadonnotificationdescription);}$vaultfactory = \mod_forum\local\container::get_vault_factory();$discussionlistvault = $vaultfactory->get_discussions_in_forum_vault();$discussionlistsortorder = get_user_preferences('forum_discussionlistsortorder',$discussionlistvault::SORTORDER_LASTPOST_DESC, $user->id);switch ($discussionlistsortorder) {case $discussionlistvault::SORTORDER_LASTPOST_DESC:$discussionlistsortorderdescription = get_string('discussionlistsortbylastpostdesc','mod_forum');break;case $discussionlistvault::SORTORDER_LASTPOST_ASC:$discussionlistsortorderdescription = get_string('discussionlistsortbylastpostasc','mod_forum');break;case $discussionlistvault::SORTORDER_CREATED_DESC:$discussionlistsortorderdescription = get_string('discussionlistsortbycreateddesc','mod_forum');break;case $discussionlistvault::SORTORDER_CREATED_ASC:$discussionlistsortorderdescription = get_string('discussionlistsortbycreatedasc','mod_forum');break;case $discussionlistvault::SORTORDER_REPLIES_DESC:$discussionlistsortorderdescription = get_string('discussionlistsortbyrepliesdesc','mod_forum');break;case $discussionlistvault::SORTORDER_REPLIES_ASC:$discussionlistsortorderdescription = get_string('discussionlistsortbyrepliesasc','mod_forum');break;case $discussionlistvault::SORTORDER_DISCUSSION_DESC:$discussionlistsortorderdescription = get_string('discussionlistsortbydiscussiondesc','mod_forum');break;case $discussionlistvault::SORTORDER_DISCUSSION_ASC:$discussionlistsortorderdescription = get_string('discussionlistsortbydiscussionasc','mod_forum');break;case $discussionlistvault::SORTORDER_STARTER_DESC:$discussionlistsortorderdescription = get_string('discussionlistsortbystarterdesc','mod_forum');break;case $discussionlistvault::SORTORDER_STARTER_ASC:$discussionlistsortorderdescription = get_string('discussionlistsortbystarterasc','mod_forum');break;case $discussionlistvault::SORTORDER_GROUP_DESC:$discussionlistsortorderdescription = get_string('discussionlistsortbygroupdesc','mod_forum');break;case $discussionlistvault::SORTORDER_GROUP_ASC:$discussionlistsortorderdescription = get_string('discussionlistsortbygroupasc','mod_forum');break;}writer::export_user_preference('mod_forum', 'forum_discussionlistsortorder',$discussionlistsortorder, $discussionlistsortorderdescription);}/*** Export all user data for the specified user, in the specified contexts.** @param approved_contextlist $contextlist The approved contexts to export information for.*/public static function export_user_data(approved_contextlist $contextlist) {global $DB;if (empty($contextlist)) {return;}$user = $contextlist->get_user();$userid = $user->id;list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);$params = $contextparams;// Digested forums.$sql = "SELECTc.id AS contextid,dig.maildigest AS maildigestFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceidJOIN {forum} f ON f.id = cm.instanceJOIN {forum_digests} dig ON dig.forum = f.idWHERE (dig.userid = :userid ANDc.id {$contextsql})";$params['userid'] = $userid;$digests = $DB->get_records_sql_menu($sql, $params);// Forum subscriptions.$sql = "SELECTc.id AS contextid,sub.userid AS subscribedFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceidJOIN {forum} f ON f.id = cm.instanceJOIN {forum_subscriptions} sub ON sub.forum = f.idWHERE (sub.userid = :userid ANDc.id {$contextsql})";$params['userid'] = $userid;$subscriptions = $DB->get_records_sql_menu($sql, $params);// Tracked forums.$sql = "SELECTc.id AS contextid,pref.userid AS trackedFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceidJOIN {forum} f ON f.id = cm.instanceJOIN {forum_track_prefs} pref ON pref.forumid = f.idWHERE (pref.userid = :userid ANDc.id {$contextsql})";$params['userid'] = $userid;$tracked = $DB->get_records_sql_menu($sql, $params);// Forum grades.$sql = "SELECTc.id AS contextid,fg.grade AS grade,f.grade_forum AS gradetypeFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceidJOIN {forum} f ON f.id = cm.instanceJOIN {forum_grades} fg ON fg.forum = f.idWHERE (fg.userid = :userid ANDc.id {$contextsql})";$params['userid'] = $userid;$grades = $DB->get_records_sql_menu($sql, $params);$sql = "SELECTc.id AS contextid,f.*,cm.id AS cmidFROM {context} cJOIN {course_modules} cm ON cm.id = c.instanceidJOIN {forum} f ON f.id = cm.instanceWHERE (c.id {$contextsql})";$params += $contextparams;// Keep a mapping of forumid to contextid.$mappings = [];$forums = $DB->get_recordset_sql($sql, $params);foreach ($forums as $forum) {$mappings[$forum->id] = $forum->contextid;$context = \context::instance_by_id($mappings[$forum->id]);// Store the main forum data.$data = request_helper::get_context_data($context, $user);writer::with_context($context)->export_data([], $data);request_helper::export_context_files($context, $user);// Store relevant metadata about this forum instance.if (isset($digests[$forum->contextid])) {static::export_digest_data($userid, $forum, $digests[$forum->contextid]);}if (isset($subscriptions[$forum->contextid])) {static::export_subscription_data($userid, $forum, $subscriptions[$forum->contextid]);}if (isset($tracked[$forum->contextid])) {static::export_tracking_data($userid, $forum, $tracked[$forum->contextid]);}if (isset($grades[$forum->contextid])) {static::export_grading_data($userid, $forum, $grades[$forum->contextid]);}}$forums->close();if (!empty($mappings)) {// Store all discussion data for this forum.static::export_discussion_data($userid, $mappings);// Store all post data for this forum.static::export_all_posts($userid, $mappings);}}/*** Store all information about all discussions that we have detected this user to have access to.** @param int $userid The userid of the user whose data is to be exported.* @param array $mappings A list of mappings from forumid => contextid.* @return array Which forums had data written for them.*/protected static function export_discussion_data(int $userid, array $mappings) {global $DB;// Find all of the discussions, and discussion subscriptions for this forum.list($foruminsql, $forumparams) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);$sql = "SELECTd.*,g.name as groupname,dsub.preferenceFROM {forum} fJOIN {forum_discussions} d ON d.forum = f.idLEFT JOIN {groups} g ON g.id = d.groupidLEFT JOIN {forum_discussion_subs} dsub ON dsub.discussion = d.id AND dsub.userid = :dsubuseridLEFT JOIN {forum_posts} p ON p.discussion = d.idWHERE f.id {$foruminsql}AND (d.userid = :discussionuserid ORp.userid = :postuserid ORdsub.id IS NOT NULL)";$params = ['postuserid' => $userid,'discussionuserid' => $userid,'dsubuserid' => $userid,];$params += $forumparams;// Keep track of the forums which have data.$forumswithdata = [];$discussions = $DB->get_recordset_sql($sql, $params);foreach ($discussions as $discussion) {// No need to take timestart into account as the user has some involvement already.// Ignore discussion timeend as it should not block access to user data.$forumswithdata[$discussion->forum] = true;$context = \context::instance_by_id($mappings[$discussion->forum]);// Store related metadata for this discussion.static::export_discussion_subscription_data($userid, $context, $discussion);$discussiondata = (object) ['name' => format_string($discussion->name, true),'pinned' => transform::yesno((bool) $discussion->pinned),'timemodified' => transform::datetime($discussion->timemodified),'usermodified' => transform::datetime($discussion->usermodified),'creator_was_you' => transform::yesno($discussion->userid == $userid),];// Store the discussion content.writer::with_context($context)->export_data(static::get_discussion_area($discussion), $discussiondata);// Forum discussions do not have any files associately directly with them.}$discussions->close();return $forumswithdata;}/*** Store all information about all posts that we have detected this user to have access to.** @param int $userid The userid of the user whose data is to be exported.* @param array $mappings A list of mappings from forumid => contextid.* @return array Which forums had data written for them.*/protected static function export_all_posts(int $userid, array $mappings) {global $DB;$commonsql = "SELECT p.discussion AS id, f.id AS forumid, d.name, d.groupidFROM {forum} fJOIN {forum_discussions} d ON d.forum = f.idJOIN {forum_posts} p ON p.discussion = d.id";// All discussions with posts authored by the user or containing private replies to the user.list($foruminsql1, $forumparams1) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);$sql1 = "{$commonsql}WHERE f.id {$foruminsql1}AND (p.userid = :postuserid OR p.privatereplyto = :privatereplyrecipient)";// All discussions with the posts marked as read by the user.list($foruminsql2, $forumparams2) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);$sql2 = "{$commonsql}JOIN {forum_read} fr ON fr.postid = p.idWHERE f.id {$foruminsql2}AND fr.userid = :readuserid";// All discussions with ratings provided by the user.list($foruminsql3, $forumparams3) = $DB->get_in_or_equal(array_keys($mappings), SQL_PARAMS_NAMED);$ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid, true);$sql3 = "{$commonsql}{$ratingsql->join}WHERE f.id {$foruminsql3}AND {$ratingsql->userwhere}";$sql = "SELECT *FROM ({$sql1} UNION {$sql2} UNION {$sql3}) unitedGROUP BY id, forumid, name, groupid";$params = ['postuserid' => $userid,'readuserid' => $userid,'privatereplyrecipient' => $userid,];$params += $forumparams1;$params += $forumparams2;$params += $forumparams3;$params += $ratingsql->params;$discussions = $DB->get_records_sql($sql, $params);foreach ($discussions as $discussion) {$context = \context::instance_by_id($mappings[$discussion->forumid]);static::export_all_posts_in_discussion($userid, $context, $discussion);}}/*** Store all information about all posts that we have detected this user to have access to.** @param int $userid The userid of the user whose data is to be exported.* @param \context $context The instance of the forum context.* @param \stdClass $discussion The discussion whose data is being exported.*/protected static function export_all_posts_in_discussion(int $userid, \context $context, \stdClass $discussion) {global $DB, $USER;$discussionid = $discussion->id;// Find all of the posts, and post subscriptions for this forum.$ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid);$sql = "SELECTp.*,d.forum AS forumid,fr.firstread,fr.lastread,fr.id AS readflag,rat.id AS hasratingsFROM {forum_discussions} dJOIN {forum_posts} p ON p.discussion = d.idLEFT JOIN {forum_read} fr ON fr.postid = p.id AND fr.userid = :readuserid{$ratingsql->join} AND {$ratingsql->userwhere}WHERE d.id = :discussionidAND (p.privatereplyto = 0OR p.privatereplyto = :privatereplyrecipientOR p.userid = :privatereplyauthor)";$params = ['discussionid' => $discussionid,'readuserid' => $userid,'privatereplyrecipient' => $userid,'privatereplyauthor' => $userid,];$params += $ratingsql->params;// Keep track of the forums which have data.$structure = (object) ['children' => [],];$posts = $DB->get_records_sql($sql, $params);foreach ($posts as $post) {$post->hasdata = (isset($post->hasdata)) ? $post->hasdata : false;$post->hasdata = $post->hasdata || !empty($post->hasratings);$post->hasdata = $post->hasdata || $post->readflag;$post->hasdata = $post->hasdata || ($post->userid == $USER->id);$post->hasdata = $post->hasdata || ($post->privatereplyto == $USER->id);if (0 == $post->parent) {$structure->children[$post->id] = $post;} else {if (empty($posts[$post->parent]->children)) {$posts[$post->parent]->children = [];}$posts[$post->parent]->children[$post->id] = $post;}// Set all parents.if ($post->hasdata) {$curpost = $post;while ($curpost->parent != 0) {$curpost = $posts[$curpost->parent];$curpost->hasdata = true;}}}$discussionarea = static::get_discussion_area($discussion);$discussionarea[] = get_string('posts', 'mod_forum');static::export_posts_in_structure($userid, $context, $discussionarea, $structure);}/*** Export all posts in the provided structure.** @param int $userid The userid of the user whose data is to be exported.* @param \context $context The instance of the forum context.* @param array $parentarea The subcontext of the parent.* @param \stdClass $structure The post structure and all of its children*/protected static function export_posts_in_structure(int $userid, \context $context, $parentarea, \stdClass $structure) {foreach ($structure->children as $post) {if (!$post->hasdata) {// This tree has no content belonging to the user. Skip it and all children.continue;}$postarea = array_merge($parentarea, static::get_post_area($post));// Store the post content.static::export_post_data($userid, $context, $postarea, $post);if (isset($post->children)) {// Now export children of this post.static::export_posts_in_structure($userid, $context, $postarea, $post);}}}/*** Export all data in the post.** @param int $userid The userid of the user whose data is to be exported.* @param \context $context The instance of the forum context.* @param array $postarea The subcontext of the parent.* @param \stdClass $post The post structure and all of its children*/protected static function export_post_data(int $userid, \context $context, $postarea, $post) {// Store related metadata.static::export_read_data($userid, $context, $postarea, $post);$postdata = (object) ['subject' => format_string($post->subject, true),'created' => transform::datetime($post->created),'modified' => transform::datetime($post->modified),'author_was_you' => transform::yesno($post->userid == $userid),];if (!empty($post->privatereplyto)) {$postdata->privatereply = transform::yesno(true);}$postdata->message = writer::with_context($context)->rewrite_pluginfile_urls($postarea, 'mod_forum', 'post', $post->id, $post->message);$postdata->message = format_text($postdata->message, $post->messageformat, (object) ['para' => false,'trusted' => $post->messagetrust,'context' => $context,]);writer::with_context($context)// Store the post.->export_data($postarea, $postdata)// Store the associated files.->export_area_files($postarea, 'mod_forum', 'post', $post->id);if ($post->userid == $userid) {// Store all ratings against this post as the post belongs to the user. All ratings on it are ratings of their content.\core_rating\privacy\provider::export_area_ratings($userid, $context, $postarea, 'mod_forum', 'post', $post->id, false);// Store all tags against this post as the tag belongs to the user.\core_tag\privacy\provider::export_item_tags($userid, $context, $postarea, 'mod_forum', 'forum_posts', $post->id);// Export all user data stored for this post from the plagiarism API.$coursecontext = $context->get_course_context();\core_plagiarism\privacy\provider::export_plagiarism_user_data($userid, $context, $postarea, ['cmid' => $context->instanceid,'course' => $coursecontext->instanceid,'forum' => $post->forumid,'discussionid' => $post->discussion,'postid' => $post->id,]);}// Check for any ratings that the user has made on this post.\core_rating\privacy\provider::export_area_ratings($userid,$context,$postarea,'mod_forum','post',$post->id,$userid,true);}/*** Store data about daily digest preferences** @param int $userid The userid of the user whose data is to be exported.* @param \stdClass $forum The forum whose data is being exported.* @param int $maildigest The mail digest setting for this forum.* @return bool Whether any data was stored.*/protected static function export_digest_data(int $userid, \stdClass $forum, int $maildigest) {if (null !== $maildigest) {// The user has a specific maildigest preference for this forum.$a = (object) ['forum' => format_string($forum->name, true),];switch ($maildigest) {case 0:$a->type = get_string('emaildigestoffshort', 'mod_forum');break;case 1:$a->type = get_string('emaildigestcompleteshort', 'mod_forum');break;case 2:$a->type = get_string('emaildigestsubjectsshort', 'mod_forum');break;}writer::with_context(\context_module::instance($forum->cmid))->export_metadata([], 'digestpreference', $maildigest,get_string('privacy:digesttypepreference', 'mod_forum', $a));return true;}return false;}/*** Store data about whether the user subscribes to forum.** @param int $userid The userid of the user whose data is to be exported.* @param \stdClass $forum The forum whose data is being exported.* @param int $subscribed if the user is subscribed* @return bool Whether any data was stored.*/protected static function export_subscription_data(int $userid, \stdClass $forum, int $subscribed) {if (null !== $subscribed) {// The user is subscribed to this forum.writer::with_context(\context_module::instance($forum->cmid))->export_metadata([], 'subscriptionpreference', 1, get_string('privacy:subscribedtoforum', 'mod_forum'));return true;}return false;}/*** Store data about whether the user subscribes to this particular discussion.** @param int $userid The userid of the user whose data is to be exported.* @param \context_module $context The instance of the forum context.* @param \stdClass $discussion The discussion whose data is being exported.* @return bool Whether any data was stored.*/protected static function export_discussion_subscription_data(int $userid, \context_module $context, \stdClass $discussion) {$area = static::get_discussion_area($discussion);if (null !== $discussion->preference) {// The user has a specific subscription preference for this discussion.$a = (object) [];switch ($discussion->preference) {case \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED:$a->preference = get_string('unsubscribed', 'mod_forum');break;default:$a->preference = get_string('subscribed', 'mod_forum');break;}writer::with_context($context)->export_metadata($area,'subscriptionpreference',$discussion->preference,get_string('privacy:discussionsubscriptionpreference', 'mod_forum', $a));return true;}return true;}/*** Store forum read-tracking data about a particular forum.** This is whether a forum has read-tracking enabled or not.** @param int $userid The userid of the user whose data is to be exported.* @param \stdClass $forum The forum whose data is being exported.* @param int $tracke if the user is subscribed* @return bool Whether any data was stored.*/protected static function export_tracking_data(int $userid, \stdClass $forum, int $tracked) {if (null !== $tracked) {// The user has a main preference to track all forums, but has opted out of this one.writer::with_context(\context_module::instance($forum->cmid))->export_metadata([], 'trackreadpreference', 0, get_string('privacy:readtrackingdisabled', 'mod_forum'));return true;}return false;}protected static function export_grading_data(int $userid, \stdClass $forum, int $grade) {global $USER;if (null !== $grade) {$context = \context_module::instance($forum->cmid);$exportpath = array_merge([],[get_string('privacy:metadata:forum_grades', 'mod_forum')]);$gradingmanager = get_grading_manager($context, 'mod_forum', 'forum');$controller = $gradingmanager->get_active_controller();// Check for advanced grading and retrieve that information.if (isset($controller)) {$gradeduser = \core_user::get_user($userid);// Fetch the gradeitem instance.$gradeitem = gradeitem::instance($controller->get_component(), $context, $controller->get_area());$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);$controllercontext = $controller->get_context();\core_grading\privacy\provider::export_item_data($controllercontext, $grade->id, $exportpath);} else {self::export_grade_data($grade, $context, $forum, $exportpath);}// The user has a grade for this forum.writer::with_context(\context_module::instance($forum->cmid))->export_metadata($exportpath, 'gradingenabled', 1, get_string('privacy:metadata:forum_grades:grade', 'mod_forum'));return true;}return false;}protected static function export_grade_data(int $grade, \context $context, \stdClass $forum, array $path) {$gradedata = (object)['forum' => $forum->name,'grade' => $grade,];writer::with_context($context)->export_data($path, $gradedata);}/*** Store read-tracking information about a particular forum post.** @param int $userid The userid of the user whose data is to be exported.* @param \context_module $context The instance of the forum context.* @param array $postarea The subcontext for this post.* @param \stdClass $post The post whose data is being exported.* @return bool Whether any data was stored.*/protected static function export_read_data(int $userid, \context_module $context, array $postarea, \stdClass $post) {if (null !== $post->firstread) {$a = (object) ['firstread' => $post->firstread,'lastread' => $post->lastread,];writer::with_context($context)->export_metadata($postarea,'postread',(object) ['firstread' => $post->firstread,'lastread' => $post->lastread,],get_string('privacy:postwasread', 'mod_forum', $a));return true;}return false;}/*** Delete all data for all users in the specified context.** @param context $context The specific context to delete data for.*/public static function delete_data_for_all_users_in_context(\context $context) {global $DB;// Check that this is a context_module.if (!$context instanceof \context_module) {return;}// Get the course module.if (!$cm = get_coursemodule_from_id('forum', $context->instanceid)) {return;}$forumid = $cm->instance;$DB->delete_records('forum_track_prefs', ['forumid' => $forumid]);$DB->delete_records('forum_subscriptions', ['forum' => $forumid]);$DB->delete_records('forum_grades', ['forum' => $forumid]);$DB->delete_records('forum_read', ['forumid' => $forumid]);$DB->delete_records('forum_digests', ['forum' => $forumid]);// Delete advanced grading information.$gradingmanager = get_grading_manager($context, 'mod_forum', 'forum');$controller = $gradingmanager->get_active_controller();if (isset($controller)) {\core_grading\privacy\provider::delete_instance_data($context);}$DB->delete_records('forum_grades', ['forum' => $forumid]);// Delete all discussion items.$DB->delete_records_select('forum_queue',"discussionid IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)",['forum' => $forumid,]);$DB->delete_records_select('forum_posts',"discussion IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)",['forum' => $forumid,]);$DB->delete_records('forum_discussion_subs', ['forum' => $forumid]);$DB->delete_records('forum_discussions', ['forum' => $forumid]);// Delete all files from the posts.$fs = get_file_storage();$fs->delete_area_files($context->id, 'mod_forum', 'post');$fs->delete_area_files($context->id, 'mod_forum', 'attachment');// Delete all ratings in the context.\core_rating\privacy\provider::delete_ratings($context, 'mod_forum', 'post');// Delete all Tags.\core_tag\privacy\provider::delete_item_tags($context, 'mod_forum', 'forum_posts');}/*** Delete all user data for the specified user, in the specified contexts.** @param approved_contextlist $contextlist The approved contexts and user information to delete information for.*/public static function delete_data_for_user(approved_contextlist $contextlist) {global $DB;$user = $contextlist->get_user();$userid = $user->id;foreach ($contextlist as $context) {// Get the course module.$cm = $DB->get_record('course_modules', ['id' => $context->instanceid]);$forum = $DB->get_record('forum', ['id' => $cm->instance]);$DB->delete_records('forum_track_prefs', ['forumid' => $forum->id,'userid' => $userid,]);$DB->delete_records('forum_subscriptions', ['forum' => $forum->id,'userid' => $userid,]);$DB->delete_records('forum_read', ['forumid' => $forum->id,'userid' => $userid,]);$DB->delete_records('forum_digests', ['forum' => $forum->id,'userid' => $userid,]);// Delete all discussion items.$DB->delete_records_select('forum_queue',"userid = :userid AND discussionid IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)",['userid' => $userid,'forum' => $forum->id,]);$DB->delete_records('forum_discussion_subs', ['forum' => $forum->id,'userid' => $userid,]);// Handle any advanced grading method data first.$grades = $DB->get_records('forum_grades', ['forum' => $forum->id, 'userid' => $user->id]);$gradingmanager = get_grading_manager($context, 'forum_grades', 'forum');$controller = $gradingmanager->get_active_controller();foreach ($grades as $grade) {// Delete advanced grading information.if (isset($controller)) {\core_grading\privacy\provider::delete_instance_data($context, $grade->id);}}// Advanced grading methods have been cleared, lets clear our module now.$DB->delete_records('forum_grades', ['forum' => $forum->id,'userid' => $userid,]);// Do not delete discussion or forum posts.// Instead update them to reflect that the content has been deleted.$postsql = "userid = :userid AND discussion IN (SELECT id FROM {forum_discussions} WHERE forum = :forum)";$postidsql = "SELECT fp.id FROM {forum_posts} fp WHERE {$postsql}";$postparams = ['forum' => $forum->id,'userid' => $userid,];// Update the subject.$DB->set_field_select('forum_posts', 'subject', '', $postsql, $postparams);// Update the message and its format.$DB->set_field_select('forum_posts', 'message', '', $postsql, $postparams);$DB->set_field_select('forum_posts', 'messageformat', FORMAT_PLAIN, $postsql, $postparams);// Mark the post as deleted.$DB->set_field_select('forum_posts', 'deleted', 1, $postsql, $postparams);// Note: Do _not_ delete ratings of other users. Only delete ratings on the users own posts.// Ratings are aggregate fields and deleting the rating of this post will have an effect on the rating// of any post.\core_rating\privacy\provider::delete_ratings_select($context, 'mod_forum', 'post',"IN ($postidsql)", $postparams);// Delete all Tags.\core_tag\privacy\provider::delete_item_tags_select($context, 'mod_forum', 'forum_posts',"IN ($postidsql)", $postparams);// Delete all files from the posts.$fs = get_file_storage();$fs->delete_area_files_select($context->id, 'mod_forum', 'post', "IN ($postidsql)", $postparams);$fs->delete_area_files_select($context->id, 'mod_forum', 'attachment', "IN ($postidsql)", $postparams);}}/*** Delete multiple users within a single context.** @param approved_userlist $userlist The approved context and user information to delete information for.*/public static function delete_data_for_users(approved_userlist $userlist) {global $DB;$context = $userlist->get_context();$cm = $DB->get_record('course_modules', ['id' => $context->instanceid]);$forum = $DB->get_record('forum', ['id' => $cm->instance]);list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);$params = array_merge(['forumid' => $forum->id], $userinparams);$DB->delete_records_select('forum_track_prefs', "forumid = :forumid AND userid {$userinsql}", $params);$DB->delete_records_select('forum_subscriptions', "forum = :forumid AND userid {$userinsql}", $params);$DB->delete_records_select('forum_read', "forumid = :forumid AND userid {$userinsql}", $params);$DB->delete_records_select('forum_queue',"userid {$userinsql} AND discussionid IN (SELECT id FROM {forum_discussions} WHERE forum = :forumid)",$params);$DB->delete_records_select('forum_discussion_subs', "forum = :forumid AND userid {$userinsql}", $params);// Do not delete discussion or forum posts.// Instead update them to reflect that the content has been deleted.$postsql = "userid {$userinsql} AND discussion IN (SELECT id FROM {forum_discussions} WHERE forum = :forumid)";$postidsql = "SELECT fp.id FROM {forum_posts} fp WHERE {$postsql}";// Update the subject.$DB->set_field_select('forum_posts', 'subject', '', $postsql, $params);// Update the subject and its format.$DB->set_field_select('forum_posts', 'message', '', $postsql, $params);$DB->set_field_select('forum_posts', 'messageformat', FORMAT_PLAIN, $postsql, $params);// Mark the post as deleted.$DB->set_field_select('forum_posts', 'deleted', 1, $postsql, $params);// Note: Do _not_ delete ratings of other users. Only delete ratings on the users own posts.// Ratings are aggregate fields and deleting the rating of this post will have an effect on the rating// of any post.\core_rating\privacy\provider::delete_ratings_select($context, 'mod_forum', 'post', "IN ($postidsql)", $params);// Delete all Tags.\core_tag\privacy\provider::delete_item_tags_select($context, 'mod_forum', 'forum_posts', "IN ($postidsql)", $params);// Delete all files from the posts.$fs = get_file_storage();$fs->delete_area_files_select($context->id, 'mod_forum', 'post', "IN ($postidsql)", $params);$fs->delete_area_files_select($context->id, 'mod_forum', 'attachment', "IN ($postidsql)", $params);list($sql, $params) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);$params['forum'] = $forum->id;// Delete advanced grading information.$grades = $DB->get_records_select('forum_grades', "forum = :forum AND userid $sql", $params);$gradeids = array_keys($grades);$gradingmanager = get_grading_manager($context, 'mod_forum', 'forum');$controller = $gradingmanager->get_active_controller();if (isset($controller)) {// Careful here, if no gradeids are provided then all data is deleted for the context.if (!empty($gradeids)) {\core_grading\privacy\provider::delete_data_for_instances($context, $gradeids);}}}}