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_glossary. 19 * 20 * @package mod_glossary 21 * @copyright 2018 Simey Lameze <simey@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace mod_glossary\privacy; 25 use core_privacy\local\metadata\collection; 26 use core_privacy\local\request\approved_contextlist; 27 use core_privacy\local\request\approved_userlist; 28 use core_privacy\local\request\contextlist; 29 use core_privacy\local\request\deletion_criteria; 30 use core_privacy\local\request\helper; 31 use core_privacy\local\request\userlist; 32 use core_privacy\local\request\writer; 33 34 defined('MOODLE_INTERNAL') || die(); 35 /** 36 * Implementation of the privacy subsystem plugin provider for the glossary activity module. 37 * 38 * @copyright 2018 Simey Lameze <simey@moodle.com> 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class provider implements 42 // This plugin stores personal data. 43 \core_privacy\local\metadata\provider, 44 // This plugin is capable of determining which users have data within it. 45 \core_privacy\local\request\core_userlist_provider, 46 // This plugin is a core_user_data_provider. 47 \core_privacy\local\request\plugin\provider { 48 49 /** 50 * Return the fields which contain personal data. 51 * 52 * @param collection $items a reference to the collection to use to store the metadata. 53 * @return collection the updated collection of metadata items. 54 */ 55 public static function get_metadata(collection $items) : collection { 56 $items->add_database_table( 57 'glossary_entries', 58 [ 59 'glossaryid' => 'privacy:metadata:glossary_entries:glossaryid', 60 'userid' => 'privacy:metadata:glossary_entries:userid', 61 'concept' => 'privacy:metadata:glossary_entries:concept', 62 'definition' => 'privacy:metadata:glossary_entries:definition', 63 'attachment' => 'privacy:metadata:glossary_entries:attachment', 64 'timemodified' => 'privacy:metadata:glossary_entries:timemodified', 65 ], 66 'privacy:metadata:glossary_entries' 67 ); 68 69 $items->add_subsystem_link('core_files', [], 'privacy:metadata:core_files'); 70 $items->add_subsystem_link('core_comment', [], 'privacy:metadata:core_comments'); 71 $items->add_subsystem_link('core_tag', [], 'privacy:metadata:core_tag'); 72 $items->add_subsystem_link('core_rating', [], 'privacy:metadata:core_rating'); 73 return $items; 74 } 75 76 /** 77 * Get the list of contexts that contain user information for the specified user. 78 * 79 * @param int $userid the userid. 80 * @return contextlist the list of contexts containing user info for the user. 81 */ 82 public static function get_contexts_for_userid(int $userid) : contextlist { 83 $contextlist = new contextlist(); 84 85 // Glossary entries. 86 $sql = "SELECT c.id 87 FROM {context} c 88 JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 89 JOIN {modules} m ON m.id = cm.module AND m.name = :modname 90 JOIN {glossary} g ON g.id = cm.instance 91 JOIN {glossary_entries} ge ON ge.glossaryid = g.id 92 WHERE ge.userid = :glossaryentryuserid"; 93 $params = [ 94 'contextlevel' => CONTEXT_MODULE, 95 'modname' => 'glossary', 96 'commentarea' => 'glossary_entry', 97 'glossaryentryuserid' => $userid, 98 ]; 99 $contextlist->add_from_sql($sql, $params); 100 101 // Where the user has rated something. 102 $ratingquery = \core_rating\privacy\provider::get_sql_join('r', 'mod_glossary', 'entry', 'ge.id', $userid, true); 103 $sql = "SELECT c.id 104 FROM {context} c 105 JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 106 JOIN {modules} m ON m.id = cm.module AND m.name = :modname 107 JOIN {glossary} g ON g.id = cm.instance 108 JOIN {glossary_entries} ge ON ge.glossaryid = g.id 109 {$ratingquery->join} 110 WHERE {$ratingquery->userwhere}"; 111 $params = [ 112 'contextlevel' => CONTEXT_MODULE, 113 'modname' => 'glossary', 114 ] + $ratingquery->params; 115 $contextlist->add_from_sql($sql, $params); 116 117 // Comments. 118 $sql = "SELECT c.id 119 FROM {context} c 120 JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 121 JOIN {modules} m ON m.id = cm.module AND m.name = :modname 122 JOIN {glossary} g ON g.id = cm.instance 123 JOIN {glossary_entries} ge ON ge.glossaryid = g.id 124 JOIN {comments} com ON com.commentarea =:commentarea AND com.itemid = ge.id 125 WHERE com.userid = :commentuserid"; 126 $params = [ 127 'contextlevel' => CONTEXT_MODULE, 128 'modname' => 'glossary', 129 'commentarea' => 'glossary_entry', 130 'commentuserid' => $userid, 131 ]; 132 $contextlist->add_from_sql($sql, $params); 133 134 return $contextlist; 135 } 136 137 /** 138 * Get the list of users who have data within a context. 139 * 140 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 141 * 142 */ 143 public static function get_users_in_context(userlist $userlist) { 144 $context = $userlist->get_context(); 145 146 if (!is_a($context, \context_module::class)) { 147 return; 148 } 149 150 // Find users with glossary entries. 151 $sql = "SELECT ge.userid 152 FROM {context} c 153 JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 154 JOIN {modules} m ON m.id = cm.module AND m.name = :modname 155 JOIN {glossary} g ON g.id = cm.instance 156 JOIN {glossary_entries} ge ON ge.glossaryid = g.id 157 WHERE c.id = :contextid"; 158 159 $params = [ 160 'contextid' => $context->id, 161 'contextlevel' => CONTEXT_MODULE, 162 'modname' => 'glossary', 163 ]; 164 165 $userlist->add_from_sql('userid', $sql, $params); 166 167 // Find users with glossary comments. 168 \core_comment\privacy\provider::get_users_in_context_from_sql($userlist, 'com', 'mod_glossary', 'glossary_entry', 169 $context->id); 170 171 // Find users with glossary ratings. 172 $sql = "SELECT ge.id 173 FROM {context} c 174 JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 175 JOIN {modules} m ON m.id = cm.module AND m.name = :modname 176 JOIN {glossary} g ON g.id = cm.instance 177 JOIN {glossary_entries} ge ON ge.glossaryid = g.id 178 WHERE c.id = :contextid"; 179 180 $params = [ 181 'contextid' => $context->id, 182 'contextlevel' => CONTEXT_MODULE, 183 'modname' => 'glossary', 184 ]; 185 186 \core_rating\privacy\provider::get_users_in_context_from_sql($userlist, 'rat', 'mod_glossary', 'entry', $sql, $params); 187 } 188 189 /** 190 * Export personal data for the given approved_contextlist. 191 * 192 * User and context information is contained within the contextlist. 193 * 194 * @param approved_contextlist $contextlist a list of contexts approved for export. 195 */ 196 public static function export_user_data(approved_contextlist $contextlist) { 197 global $DB; 198 199 if (empty($contextlist->count())) { 200 return; 201 } 202 203 $user = $contextlist->get_user(); 204 list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); 205 $ratingquery = \core_rating\privacy\provider::get_sql_join('r', 'mod_glossary', 'entry', 'ge.id', $user->id); 206 207 $sql = "SELECT ge.id as entryid, 208 cm.id AS cmid, 209 ge.userid, 210 ge.concept, 211 ge.definition, 212 ge.definitionformat, 213 ge.attachment, 214 ge.timecreated, 215 ge.timemodified 216 FROM {glossary_entries} ge 217 JOIN {glossary} g ON ge.glossaryid = g.id 218 JOIN {course_modules} cm ON g.id = cm.instance 219 JOIN {modules} m ON cm.module = m.id AND m.name = :modulename 220 JOIN {context} c ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 221 LEFT JOIN {comments} com ON com.itemid = ge.id AND com.commentarea = :commentarea AND com.userid = :commentuserid 222 {$ratingquery->join} 223 WHERE c.id {$contextsql} 224 AND (ge.userid = :userid OR com.id IS NOT NULL OR {$ratingquery->userwhere}) 225 ORDER BY ge.id, cm.id"; 226 $params = [ 227 'userid' => $user->id, 228 'modulename' => 'glossary', 229 'contextlevel' => CONTEXT_MODULE, 230 'commentarea' => 'glossary_entry', 231 'commentuserid' => $user->id 232 ] + $contextparams; 233 $params = array_merge($params, $ratingquery->params); 234 $glossaryentries = $DB->get_recordset_sql($sql, $params); 235 236 // Reference to the glossary activity seen in the last iteration of the loop. By comparing this with the 237 // current record, and because we know the results are ordered, we know when we've moved to the entries 238 // for a new glossary activity and therefore when we can export the complete data for the last activity. 239 $lastcmid = null; 240 241 $glossarydata = []; 242 foreach ($glossaryentries as $record) { 243 $concept = format_string($record->concept); 244 $path = array_merge([get_string('entries', 'mod_glossary'), $concept . " ({$record->entryid})"]); 245 246 // If we've moved to a new glossary, then write the last glossary data and reinit the glossary data array. 247 if (!is_null($lastcmid)) { 248 if ($lastcmid != $record->cmid) { 249 if (!empty($glossarydata)) { 250 $context = \context_module::instance($lastcmid); 251 self::export_glossary_data_for_user($glossarydata, $context, [], $user); 252 $glossarydata = []; 253 } 254 } 255 } 256 $lastcmid = $record->cmid; 257 $context = \context_module::instance($lastcmid); 258 259 // Export files added on the glossary entry definition field. 260 $definition = format_text(writer::with_context($context)->rewrite_pluginfile_urls($path, 'mod_glossary', 261 'entry', $record->entryid, $record->definition), $record->definitionformat); 262 263 // Export just the files attached to this user entry. 264 if ($record->userid == $user->id) { 265 // Get all files attached to the glossary attachment. 266 writer::with_context($context)->export_area_files($path, 'mod_glossary', 'entry', $record->entryid); 267 268 // Get all files attached to the glossary attachment. 269 writer::with_context($context)->export_area_files($path, 'mod_glossary', 'attachment', $record->entryid); 270 } 271 272 // Export associated comments. 273 \core_comment\privacy\provider::export_comments($context, 'mod_glossary', 'glossary_entry', 274 $record->entryid, $path, $record->userid != $user->id); 275 276 // Export associated tags. 277 \core_tag\privacy\provider::export_item_tags($user->id, $context, $path, 'mod_glossary', 'glossary_entries', 278 $record->entryid, $record->userid != $user->id); 279 280 // Export associated ratings. 281 \core_rating\privacy\provider::export_area_ratings($user->id, $context, $path, 'mod_glossary', 'entry', 282 $record->entryid, $record->userid != $user->id); 283 284 $glossarydata['entries'][] = [ 285 'concept' => $record->concept, 286 'definition' => $definition, 287 'timecreated' => \core_privacy\local\request\transform::datetime($record->timecreated), 288 'timemodified' => \core_privacy\local\request\transform::datetime($record->timemodified) 289 ]; 290 } 291 $glossaryentries->close(); 292 293 // The data for the last activity won't have been written yet, so make sure to write it now! 294 if (!empty($glossarydata)) { 295 $context = \context_module::instance($lastcmid); 296 self::export_glossary_data_for_user($glossarydata, $context, [], $user); 297 } 298 } 299 300 /** 301 * Export the supplied personal data for a single glossary activity, along with any generic data or area files. 302 * 303 * @param array $glossarydata The personal data to export for the glossary. 304 * @param \context_module $context The context of the glossary. 305 * @param array $subcontext The location within the current context that this data belongs. 306 * @param \stdClass $user the user record 307 */ 308 protected static function export_glossary_data_for_user(array $glossarydata, \context_module $context, 309 array $subcontext, \stdClass $user) { 310 // Fetch the generic module data for the glossary. 311 $contextdata = helper::get_context_data($context, $user); 312 // Merge with glossary data and write it. 313 $contextdata = (object)array_merge((array)$contextdata, $glossarydata); 314 writer::with_context($context)->export_data($subcontext, $contextdata); 315 // Write generic module intro files. 316 helper::export_context_files($context, $user); 317 } 318 319 /** 320 * Delete all data for all users in the specified context. 321 * 322 * @param \context $context the context to delete in. 323 */ 324 public static function delete_data_for_all_users_in_context(\context $context) { 325 global $DB; 326 327 if ($context->contextlevel != CONTEXT_MODULE) { 328 return; 329 } 330 331 if (!$cm = get_coursemodule_from_id('glossary', $context->instanceid)) { 332 return; 333 } 334 335 $instanceid = $cm->instance; 336 337 $entries = $DB->get_records('glossary_entries', ['glossaryid' => $instanceid], '', 'id'); 338 339 // Delete related entry aliases. 340 $DB->delete_records_list('glossary_alias', 'entryid', array_keys($entries)); 341 342 // Delete related entry categories. 343 $DB->delete_records_list('glossary_entries_categories', 'entryid', array_keys($entries)); 344 345 // Delete entry and attachment files. 346 get_file_storage()->delete_area_files($context->id, 'mod_glossary', 'entry'); 347 get_file_storage()->delete_area_files($context->id, 'mod_glossary', 'attachment'); 348 349 // Delete related ratings. 350 \core_rating\privacy\provider::delete_ratings($context, 'mod_glossary', 'entry'); 351 352 // Delete comments. 353 \core_comment\privacy\provider::delete_comments_for_all_users($context, 'mod_glossary', 'glossary_entry'); 354 355 // Delete tags. 356 \core_tag\privacy\provider::delete_item_tags($context, 'mod_glossary', 'glossary_entries'); 357 358 // Now delete all user related entries. 359 $DB->delete_records('glossary_entries', ['glossaryid' => $instanceid]); 360 } 361 362 /** 363 * Delete all user data for the specified user, in the specified contexts. 364 * 365 * @param approved_contextlist $contextlist a list of contexts approved for deletion. 366 */ 367 public static function delete_data_for_user(approved_contextlist $contextlist) { 368 global $DB; 369 370 if (empty($contextlist->count())) { 371 return; 372 } 373 374 $userid = $contextlist->get_user()->id; 375 foreach ($contextlist->get_contexts() as $context) { 376 if ($context->contextlevel == CONTEXT_MODULE) { 377 378 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST); 379 380 $entries = $DB->get_records('glossary_entries', ['glossaryid' => $instanceid, 'userid' => $userid], 381 '', 'id'); 382 383 if (!$entries) { 384 continue; 385 } 386 387 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($entries), SQL_PARAMS_NAMED); 388 // Delete related entry aliases. 389 $DB->delete_records_list('glossary_alias', 'entryid', array_keys($entries)); 390 391 // Delete related entry categories. 392 $DB->delete_records_list('glossary_entries_categories', 'entryid', array_keys($entries)); 393 394 // Delete related entry and attachment files. 395 get_file_storage()->delete_area_files_select($context->id, 'mod_glossary', 'entry', $insql, $inparams); 396 get_file_storage()->delete_area_files_select($context->id, 'mod_glossary', 'attachment', $insql, $inparams); 397 398 // Delete user tags related to this glossary. 399 \core_tag\privacy\provider::delete_item_tags_select($context, 'mod_glossary', 'glossary_entries', $insql, $inparams); 400 401 // Delete related ratings. 402 \core_rating\privacy\provider::delete_ratings_select($context, 'mod_glossary', 'entry', $insql, $inparams); 403 404 // Delete comments. 405 \core_comment\privacy\provider::delete_comments_for_user($contextlist, 'mod_glossary', 'glossary_entry'); 406 407 // Now delete all user related entries. 408 $DB->delete_records('glossary_entries', ['glossaryid' => $instanceid, 'userid' => $userid]); 409 } 410 } 411 } 412 413 /** 414 * Delete multiple users within a single context. 415 * 416 * @param approved_userlist $userlist The approved context and user information to delete information for. 417 */ 418 public static function delete_data_for_users(approved_userlist $userlist) { 419 global $DB; 420 421 $context = $userlist->get_context(); 422 $userids = $userlist->get_userids(); 423 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST); 424 list($userinsql, $userinparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 425 426 $glossaryentrieswhere = "glossaryid = :instanceid AND userid {$userinsql}"; 427 $userinstanceparams = $userinparams + ['instanceid' => $instanceid]; 428 429 $entriesobject = $DB->get_recordset_select('glossary_entries', $glossaryentrieswhere, $userinstanceparams, 'id', 'id'); 430 $entries = []; 431 432 foreach ($entriesobject as $entry) { 433 $entries[] = $entry->id; 434 } 435 436 $entriesobject->close(); 437 438 if (!$entries) { 439 return; 440 } 441 442 list($insql, $inparams) = $DB->get_in_or_equal($entries, SQL_PARAMS_NAMED); 443 444 // Delete related entry aliases. 445 $DB->delete_records_list('glossary_alias', 'entryid', $entries); 446 447 // Delete related entry categories. 448 $DB->delete_records_list('glossary_entries_categories', 'entryid', $entries); 449 450 // Delete related entry and attachment files. 451 get_file_storage()->delete_area_files_select($context->id, 'mod_glossary', 'entry', $insql, $inparams); 452 get_file_storage()->delete_area_files_select($context->id, 'mod_glossary', 'attachment', $insql, $inparams); 453 454 // Delete user tags related to this glossary. 455 \core_tag\privacy\provider::delete_item_tags_select($context, 'mod_glossary', 'glossary_entries', $insql, $inparams); 456 457 // Delete related ratings. 458 \core_rating\privacy\provider::delete_ratings_select($context, 'mod_glossary', 'entry', $insql, $inparams); 459 460 // Delete comments. 461 \core_comment\privacy\provider::delete_comments_for_users($userlist, 'mod_glossary', 'glossary_entry'); 462 463 // Now delete all user related entries. 464 $deletewhere = "glossaryid = :instanceid AND userid {$userinsql}"; 465 $DB->delete_records_select('glossary_entries', $deletewhere, $userinstanceparams); 466 } 467 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body