See Release Notes
Long Term Support Release
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 core_group. 19 * 20 * @package core_group 21 * @category privacy 22 * @copyright 2018 Shamim Rezaie <shamim@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace core_group\privacy; 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 use core_privacy\local\metadata\collection; 31 use core_privacy\local\request\approved_contextlist; 32 use core_privacy\local\request\approved_userlist; 33 use core_privacy\local\request\contextlist; 34 use core_privacy\local\request\transform; 35 use core_privacy\local\request\userlist; 36 37 /** 38 * Privacy Subsystem implementation for core_group. 39 * 40 * @copyright 2018 Shamim Rezaie <shamim@moodle.com> 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class provider implements 44 // Groups store user data. 45 \core_privacy\local\metadata\provider, 46 47 // The group subsystem contains user's group memberships. 48 \core_privacy\local\request\subsystem\provider, 49 50 // The group subsystem can provide information to other plugins. 51 \core_privacy\local\request\subsystem\plugin_provider, 52 53 // This plugin is capable of determining which users have data within it. 54 \core_privacy\local\request\core_userlist_provider, 55 \core_privacy\local\request\shared_userlist_provider 56 { 57 58 /** 59 * Returns meta data about this system. 60 * 61 * @param collection $collection The initialised collection to add items to. 62 * @return collection A listing of user data stored through this system. 63 */ 64 public static function get_metadata(collection $collection) : collection { 65 $collection->add_database_table('groups_members', [ 66 'groupid' => 'privacy:metadata:groups:groupid', 67 'userid' => 'privacy:metadata:groups:userid', 68 'timeadded' => 'privacy:metadata:groups:timeadded', 69 ], 'privacy:metadata:groups'); 70 71 $collection->link_subsystem('core_message', 'privacy:metadata:core_message'); 72 73 return $collection; 74 } 75 76 /** 77 * Writes user data to the writer for the user to download. 78 * 79 * @param \context $context The context to export data for. 80 * @param string $component The component that is calling this function. Empty string means no component. 81 * @param array $subcontext The sub-context in which to export this data. 82 * @param int $itemid Optional itemid associated with component. 83 */ 84 public static function export_groups(\context $context, string $component, array $subcontext = [], int $itemid = 0) { 85 global $DB, $USER; 86 87 if (!$context instanceof \context_course) { 88 return; 89 } 90 91 $subcontext[] = get_string('groups', 'core_group'); 92 93 $sql = "SELECT gm.id, gm.timeadded, gm.userid, g.name, gm.groupid 94 FROM {groups_members} gm 95 JOIN {groups} g ON gm.groupid = g.id 96 WHERE g.courseid = :courseid 97 AND gm.component = :component 98 AND gm.userid = :userid"; 99 $params = [ 100 'courseid' => $context->instanceid, 101 'component' => $component, 102 'userid' => $USER->id 103 ]; 104 105 if ($itemid) { 106 $sql .= ' AND gm.itemid = :itemid'; 107 $params['itemid'] = $itemid; 108 } 109 110 $groups = $DB->get_records_sql($sql, $params); 111 112 $groupstoexport = array_map(function($group) { 113 return (object) [ 114 'name' => format_string($group->name), 115 'timeadded' => transform::datetime($group->timeadded), 116 ]; 117 }, $groups); 118 119 if (!empty($groups)) { 120 \core_privacy\local\request\writer::with_context($context) 121 ->export_data($subcontext, (object) [ 122 'groups' => $groupstoexport, 123 ]); 124 125 foreach ($groups as $group) { 126 // Export associated conversations to this group. 127 \core_message\privacy\provider::export_conversations($USER->id, 'core_group', 'groups', 128 $context, [], $group->groupid); 129 } 130 } 131 } 132 133 /** 134 * Deletes all group memberships for a specified context and component. 135 * 136 * @param \context $context Details about which context to delete group memberships for. 137 * @param string $component Component to delete. Empty string means no component (manual group memberships). 138 * @param int $itemid Optional itemid associated with component. 139 */ 140 public static function delete_groups_for_all_users(\context $context, string $component, int $itemid = 0) { 141 global $DB; 142 143 if (!$context instanceof \context_course) { 144 return; 145 } 146 147 if (!$DB->record_exists('groups', ['courseid' => $context->instanceid])) { 148 return; 149 } 150 151 $select = "component = :component AND groupid IN (SELECT g.id FROM {groups} g WHERE courseid = :courseid)"; 152 $params = ['component' => $component, 'courseid' => $context->instanceid]; 153 154 if ($itemid) { 155 $select .= ' AND itemid = :itemid'; 156 $params['itemid'] = $itemid; 157 } 158 159 // Delete the group conversations. 160 $groups = $DB->get_records_select('groups_members', $select, $params); 161 foreach ($groups as $group) { 162 \core_message\privacy\provider::delete_conversations_for_all_users($context, 'core_group', 'groups', $group->groupid); 163 } 164 165 // Remove members from the group. 166 $DB->delete_records_select('groups_members', $select, $params); 167 168 // Purge the group and grouping cache for users. 169 \cache_helper::purge_by_definition('core', 'user_group_groupings'); 170 } 171 172 /** 173 * Deletes all records for a user from a list of approved contexts. 174 * 175 * @param approved_contextlist $contextlist Contains the user ID and a list of contexts to be deleted from. 176 * @param string $component Component to delete from. Empty string means no component (manual memberships). 177 * @param int $itemid Optional itemid associated with component. 178 */ 179 public static function delete_groups_for_user(approved_contextlist $contextlist, string $component, int $itemid = 0) { 180 global $DB; 181 182 $userid = $contextlist->get_user()->id; 183 184 $contextids = $contextlist->get_contextids(); 185 186 if (!$contextids) { 187 return; 188 } 189 190 list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED); 191 $contextparams += ['contextcourse' => CONTEXT_COURSE]; 192 $groupselect = "SELECT g.id 193 FROM {groups} g 194 JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse 195 WHERE ctx.id $contextsql"; 196 197 if (!$DB->record_exists_sql($groupselect, $contextparams)) { 198 return; 199 } 200 201 $select = "userid = :userid AND component = :component AND groupid IN ({$groupselect})"; 202 $params = ['userid' => $userid, 'component' => $component] + $contextparams; 203 204 if ($itemid) { 205 $select .= ' AND itemid = :itemid'; 206 $params['itemid'] = $itemid; 207 } 208 209 // Delete the group conversations. 210 $groups = $DB->get_records_select('groups_members', $select, $params); 211 foreach ($groups as $group) { 212 \core_message\privacy\provider::delete_conversations_for_user($contextlist, 'core_group', 'groups', $group->groupid); 213 } 214 215 // Remove members from the group. 216 $DB->delete_records_select('groups_members', $select, $params); 217 218 // Invalidate the group and grouping cache for the user. 219 \cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($userid)); 220 } 221 222 /** 223 * Add the list of users who are members of some groups in the specified constraints. 224 * 225 * @param userlist $userlist The userlist to add the users to. 226 * @param string $component The component to check. 227 * @param int $itemid Optional itemid associated with component. 228 */ 229 public static function get_group_members_in_context(userlist $userlist, string $component, int $itemid = 0) { 230 $context = $userlist->get_context(); 231 232 if (!$context instanceof \context_course) { 233 return; 234 } 235 236 // Group members in the given context. 237 $sql = "SELECT gm.userid 238 FROM {groups_members} gm 239 JOIN {groups} g ON gm.groupid = g.id 240 WHERE g.courseid = :courseid AND gm.component = :component"; 241 $params = [ 242 'courseid' => $context->instanceid, 243 'component' => $component 244 ]; 245 246 if ($itemid) { 247 $sql .= ' AND gm.itemid = :itemid'; 248 $params['itemid'] = $itemid; 249 } 250 251 $userlist->add_from_sql('userid', $sql, $params); 252 253 // Get the users with some group conversation in this context. 254 \core_message\privacy\provider::add_conversations_in_context($userlist, 'core_group', 'groups', $itemid); 255 } 256 257 /** 258 * Deletes all records for multiple users within a single context. 259 * 260 * @param approved_userlist $userlist The approved context and user information to delete information for. 261 * @param string $component Component to delete from. Empty string means no component (manual memberships). 262 * @param int $itemid Optional itemid associated with component. 263 */ 264 public static function delete_groups_for_users(approved_userlist $userlist, string $component, int $itemid = 0) { 265 global $DB; 266 267 $context = $userlist->get_context(); 268 $userids = $userlist->get_userids(); 269 270 if (!$context instanceof \context_course) { 271 return; 272 } 273 274 list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 275 276 $groupselect = "SELECT id FROM {groups} WHERE courseid = :courseid"; 277 $groupparams = ['courseid' => $context->instanceid]; 278 279 $select = "component = :component AND userid {$usersql} AND groupid IN ({$groupselect})"; 280 $params = ['component' => $component] + $groupparams + $userparams; 281 282 if ($itemid) { 283 $select .= ' AND itemid = :itemid'; 284 $params['itemid'] = $itemid; 285 } 286 287 // Delete the group conversations for these users. 288 $groups = $DB->get_records_select('groups_members', $select, $params); 289 foreach ($groups as $group) { 290 \core_message\privacy\provider::delete_conversations_for_users($userlist, 'core_group', 'groups', $group->groupid); 291 } 292 293 $DB->delete_records_select('groups_members', $select, $params); 294 295 // Invalidate the group and grouping cache for the user. 296 \cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), $userids); 297 } 298 299 /** 300 * Get the list of contexts that contain group membership for the specified user. 301 * 302 * @param int $userid The user to search. 303 * @param string $component The component to check. 304 * @param int $itemid Optional itemid associated with component. 305 * @return contextlist The contextlist containing the list of contexts. 306 */ 307 public static function get_contexts_for_group_member(int $userid, string $component, int $itemid = 0) { 308 $contextlist = new contextlist(); 309 310 $sql = "SELECT ctx.id 311 FROM {groups_members} gm 312 JOIN {groups} g ON gm.groupid = g.id 313 JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse 314 WHERE gm.userid = :userid AND gm.component = :component"; 315 316 $params = [ 317 'contextcourse' => CONTEXT_COURSE, 318 'userid' => $userid, 319 'component' => $component 320 ]; 321 322 if ($itemid) { 323 $sql .= ' AND gm.itemid = :itemid'; 324 $params['itemid'] = $itemid; 325 } 326 327 $contextlist->add_from_sql($sql, $params); 328 329 // Get the contexts where the userid has group conversations. 330 \core_message\privacy\provider::add_contexts_for_conversations($contextlist, $userid, 'core_group', 'groups', $itemid); 331 332 return $contextlist; 333 } 334 335 /** 336 * Get the list of users who have data within a context. 337 * 338 * @param int $userid The user to search. 339 * @return contextlist The contextlist containing the list of contexts used in this plugin. 340 */ 341 public static function get_contexts_for_userid(int $userid) : contextlist { 342 return static::get_contexts_for_group_member($userid, ''); 343 } 344 345 /** 346 * Get the list of users who have data within a context. 347 * 348 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 349 */ 350 public static function get_users_in_context(userlist $userlist) { 351 $context = $userlist->get_context(); 352 353 if (!$context instanceof \context_course) { 354 return; 355 } 356 357 static::get_group_members_in_context($userlist, ''); 358 } 359 360 /** 361 * Export all user data for the specified user, in the specified contexts. 362 * 363 * @param approved_contextlist $contextlist The approved contexts to export information for. 364 */ 365 public static function export_user_data(approved_contextlist $contextlist) { 366 $contexts = $contextlist->get_contexts(); 367 368 foreach ($contexts as $context) { 369 static::export_groups($context, ''); 370 } 371 } 372 373 /** 374 * Delete all data for all users in the specified context. 375 * 376 * @param context $context The specific context to delete data for. 377 */ 378 public static function delete_data_for_all_users_in_context(\context $context) { 379 static::delete_groups_for_all_users($context, ''); 380 } 381 382 /** 383 * Delete all user data for the specified user, in the specified contexts. 384 * 385 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 386 */ 387 public static function delete_data_for_user(approved_contextlist $contextlist) { 388 static::delete_groups_for_user($contextlist, ''); 389 } 390 391 /** 392 * Delete multiple users within a single context. 393 * 394 * @param approved_userlist $userlist The approved context and user information to delete information for. 395 */ 396 public static function delete_data_for_users(approved_userlist $userlist) { 397 static::delete_groups_for_users($userlist, ''); 398 } 399 400 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body