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_assignment. 19 * 20 * @package mod_assignment 21 * @copyright 2018 Zig Tan <zig@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace mod_assignment\privacy; 26 27 use core_privacy\local\metadata\collection; 28 use core_privacy\local\request\approved_contextlist; 29 use core_privacy\local\request\contextlist; 30 use core_privacy\local\request\transform; 31 use core_privacy\local\request\writer; 32 use core_privacy\local\request\helper; 33 use core_privacy\local\request\approved_userlist; 34 use core_privacy\local\request\userlist; 35 36 defined('MOODLE_INTERNAL') || die(); 37 38 global $CFG; 39 require_once($CFG->dirroot . '/mod/assignment/lib.php'); 40 41 /** 42 * Implementation of the privacy subsystem plugin provider for mod_assignment. 43 * 44 * @copyright 2018 Zig Tan <zig@moodle.com> 45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 46 */ 47 class provider implements 48 \core_privacy\local\metadata\provider, 49 \core_privacy\local\request\plugin\provider, 50 \core_privacy\local\request\user_preference_provider, 51 \core_privacy\local\request\core_userlist_provider { 52 53 /** 54 * Return the fields which contain personal data. 55 * 56 * @param collection $collection a reference to the collection to use to store the metadata. 57 * @return collection the updated collection of metadata items. 58 */ 59 public static function get_metadata(collection $collection) : collection { 60 $collection->add_database_table( 61 'assignment_submissions', 62 [ 63 'userid' => 'privacy:metadata:assignment_submissions:userid', 64 'timecreated' => 'privacy:metadata:assignment_submissions:timecreated', 65 'timemodified' => 'privacy:metadata:assignment_submissions:timemodified', 66 'numfiles' => 'privacy:metadata:assignment_submissions:numfiles', 67 'data1' => 'privacy:metadata:assignment_submissions:data1', 68 'data2' => 'privacy:metadata:assignment_submissions:data2', 69 'grade' => 'privacy:metadata:assignment_submissions:grade', 70 'submissioncomment' => 'privacy:metadata:assignment_submissions:submissioncomment', 71 'teacher' => 'privacy:metadata:assignment_submissions:teacher', 72 'timemarked' => 'privacy:metadata:assignment_submissions:timemarked', 73 'mailed' => 'privacy:metadata:assignment_submissions:mailed' 74 ], 75 'privacy:metadata:assignment_submissions' 76 ); 77 78 // Legacy mod_assignment preferences from Moodle 2.X. 79 $collection->add_user_preference('assignment_filter', 'privacy:metadata:assignmentfilter'); 80 $collection->add_user_preference('assignment_mailinfo', 'privacy:metadata:assignmentmailinfo'); 81 $collection->add_user_preference('assignment_perpage', 'privacy:metadata:assignmentperpage'); 82 $collection->add_user_preference('assignment_quickgrade', 'privacy:metadata:assignmentquickgrade'); 83 84 return $collection; 85 } 86 87 /** 88 * Get the list of contexts that contain user information for the specified user. 89 * 90 * @param int $userid the userid. 91 * @return contextlist the list of contexts containing user info for the user. 92 */ 93 public static function get_contexts_for_userid(int $userid) : contextlist { 94 $contextlist = new contextlist(); 95 96 $sql = "SELECT DISTINCT 97 ctx.id 98 FROM {context} ctx 99 JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule 100 JOIN {modules} m ON cm.module = m.id AND m.name = :modulename 101 JOIN {assignment} a ON cm.instance = a.id 102 JOIN {assignment_submissions} s ON s.assignment = a.id 103 WHERE s.userid = :userid 104 OR s.teacher = :teacher"; 105 106 $params = [ 107 'contextmodule' => CONTEXT_MODULE, 108 'modulename' => 'assignment', 109 'userid' => $userid, 110 'teacher' => $userid 111 ]; 112 113 $contextlist->add_from_sql($sql, $params); 114 115 return $contextlist; 116 } 117 118 /** 119 * Get the list of users who have data within a context. 120 * 121 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 122 */ 123 public static function get_users_in_context(userlist $userlist) { 124 $context = $userlist->get_context(); 125 if ($context->contextlevel != CONTEXT_MODULE) { 126 return; 127 } 128 129 $params = [ 130 'modulename' => 'assignment', 131 'contextlevel' => CONTEXT_MODULE, 132 'contextid' => $context->id 133 ]; 134 $sql = "SELECT s.userid 135 FROM {assignment_submissions} s 136 JOIN {assignment} a ON s.assignment = a.id 137 JOIN {modules} m ON m.name = :modulename 138 JOIN {course_modules} cm ON a.id = cm.instance AND cm.module = m.id 139 JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel 140 WHERE ctx.id = :contextid 141 "; 142 $userlist->add_from_sql('userid', $sql, $params); 143 144 $sql = "SELECT s.teacher 145 FROM {assignment_submissions} s 146 JOIN {assignment} a ON s.assignment = a.id 147 JOIN {modules} m ON m.name = :modulename 148 JOIN {course_modules} cm ON a.id = cm.instance AND cm.module = m.id 149 JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel 150 WHERE ctx.id = :contextid 151 "; 152 $userlist->add_from_sql('teacher', $sql, $params); 153 } 154 155 /** 156 * Export personal data for the given approved_contextlist. 157 * User and context information is contained within the contextlist. 158 * 159 * @param approved_contextlist $contextlist a list of contexts approved for export. 160 */ 161 public static function export_user_data(approved_contextlist $contextlist) { 162 if (empty($contextlist->count())) { 163 return; 164 } 165 166 $user = $contextlist->get_user(); 167 168 foreach ($contextlist->get_contexts() as $context) { 169 if ($context->contextlevel != CONTEXT_MODULE) { 170 continue; 171 } 172 173 // Cannot make use of helper::export_context_files(), need to manually export assignment details. 174 $assignmentdata = self::get_assignment_by_context($context); 175 176 // Get assignment details object for output. 177 $assignment = self::get_assignment_output($assignmentdata); 178 writer::with_context($context)->export_data([], $assignment); 179 180 // Check if the user has marked any assignment's submissions to determine assignment submissions to export. 181 $teacher = (self::has_marked_assignment_submissions($assignmentdata->id, $user->id) == true) ? true : false; 182 183 // Get the assignment submissions submitted by & marked by the user for an assignment. 184 $submissionsdata = self::get_assignment_submissions_by_assignment($assignmentdata->id, $user->id, $teacher); 185 186 foreach ($submissionsdata as $submissiondata) { 187 // Default subcontext path to export assignment submissions submitted by the user. 188 $subcontexts = [ 189 get_string('privacy:submissionpath', 'mod_assignment') 190 ]; 191 192 if ($teacher == true) { 193 if ($submissiondata->teacher == $user->id) { 194 // Export assignment submissions that have been marked by the user. 195 $subcontexts = [ 196 get_string('privacy:markedsubmissionspath', 'mod_assignment'), 197 transform::user($submissiondata->userid) 198 ]; 199 } 200 } 201 202 // Get assignment submission details object for output. 203 $submission = self::get_assignment_submission_output($submissiondata); 204 $itemid = $submissiondata->id; 205 206 writer::with_context($context) 207 ->export_data($subcontexts, $submission) 208 ->export_area_files($subcontexts, 'mod_assignment', 'submission', $itemid); 209 } 210 } 211 } 212 213 /** 214 * Stores the user preferences related to mod_assign. 215 * 216 * @param int $userid The user ID that we want the preferences for. 217 */ 218 public static function export_user_preferences(int $userid) { 219 $context = \context_system::instance(); 220 $assignmentpreferences = [ 221 'assignment_filter' => [ 222 'string' => get_string('privacy:metadata:assignmentfilter', 'mod_assignment'), 223 'bool' => false 224 ], 225 'assignment_mailinfo' => [ 226 'string' => get_string('privacy:metadata:assignmentmailinfo', 'mod_assignment'), 227 'bool' => false 228 ], 229 'assignment_perpage' => [ 230 'string' => get_string('privacy:metadata:assignmentperpage', 'mod_assignment'), 231 'bool' => false 232 ], 233 'assignment_quickgrade' => [ 234 'string' => get_string('privacy:metadata:assignmentquickgrade', 'mod_assignment'), 235 'bool' => false 236 ], 237 ]; 238 foreach ($assignmentpreferences as $key => $preference) { 239 $value = get_user_preferences($key, null, $userid); 240 if ($preference['bool']) { 241 $value = transform::yesno($value); 242 } 243 if (isset($value)) { 244 writer::with_context($context) 245 ->export_user_preference('mod_assignment', $key, $value, $preference['string']); 246 } 247 } 248 } 249 250 /** 251 * Delete all data for all users in the specified context. 252 * 253 * @param \context $context the context to delete in. 254 */ 255 public static function delete_data_for_all_users_in_context(\context $context) { 256 global $DB; 257 258 if ($context->contextlevel == CONTEXT_MODULE) { 259 // Delete all assignment submissions for the assignment associated with the context module. 260 $assignment = self::get_assignment_by_context($context); 261 if ($assignment != null) { 262 $DB->delete_records('assignment_submissions', ['assignment' => $assignment->id]); 263 264 // Delete all file uploads associated with the assignment submission for the specified context. 265 $fs = get_file_storage(); 266 $fs->delete_area_files($context->id, 'mod_assignment', 'submission'); 267 } 268 } 269 } 270 271 /** 272 * Delete all user data for the specified user, in the specified contexts. 273 * 274 * @param approved_contextlist $contextlist a list of contexts approved for deletion. 275 */ 276 public static function delete_data_for_user(approved_contextlist $contextlist) { 277 global $DB; 278 279 if (empty($contextlist->count())) { 280 return; 281 } 282 283 $userid = $contextlist->get_user()->id; 284 285 // Only retrieve assignment submissions submitted by the user for deletion. 286 $assignmentsubmissionids = array_keys(self::get_assignment_submissions_by_contextlist($contextlist, $userid)); 287 $DB->delete_records_list('assignment_submissions', 'id', $assignmentsubmissionids); 288 289 // Delete all file uploads associated with the assignment submission for the user's specified list of contexts. 290 $fs = get_file_storage(); 291 foreach ($contextlist->get_contextids() as $contextid) { 292 foreach ($assignmentsubmissionids as $submissionid) { 293 $fs->delete_area_files($contextid, 'mod_assignment', 'submission', $submissionid); 294 } 295 } 296 } 297 298 /** 299 * Delete multiple users within a single context. 300 * 301 * @param approved_userlist $userlist The approved context and user information to delete information for. 302 */ 303 public static function delete_data_for_users(approved_userlist $userlist) { 304 global $DB; 305 306 $context = $userlist->get_context(); 307 // If the context isn't for a module then return early. 308 if ($context->contextlevel != CONTEXT_MODULE) { 309 return; 310 } 311 // Fetch the assignment. 312 $assignment = self::get_assignment_by_context($context); 313 $userids = $userlist->get_userids(); 314 315 list($inorequalsql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 316 $params['assignmentid'] = $assignment->id; 317 318 // Get submission ids. 319 $sql = " 320 SELECT s.id 321 FROM {assignment_submissions} s 322 JOIN {assignment} a ON s.assignment = a.id 323 WHERE a.id = :assignmentid 324 AND s.userid $inorequalsql 325 "; 326 327 $submissionids = $DB->get_records_sql($sql, $params); 328 list($submissionidsql, $submissionparams) = $DB->get_in_or_equal(array_keys($submissionids), SQL_PARAMS_NAMED); 329 $fs = get_file_storage(); 330 $fs->delete_area_files_select($context->id, 'mod_assignment', 'submission', $submissionidsql, $submissionparams); 331 // Delete related tables. 332 $DB->delete_records_list('assignment_submissions', 'id', array_keys($submissionids)); 333 } 334 335 // Start of helper functions. 336 337 /** 338 * Helper function to check if a user has marked assignment submissions for a given assignment. 339 * 340 * @param int $assignmentid The assignment ID to check if user has marked associated submissions. 341 * @param int $userid The user ID to check if user has marked associated submissions. 342 * @return bool If user has marked associated submissions returns true, otherwise false. 343 * @throws \dml_exception 344 */ 345 protected static function has_marked_assignment_submissions($assignmentid, $userid) { 346 global $DB; 347 348 $params = [ 349 'assignment' => $assignmentid, 350 'teacher' => $userid 351 ]; 352 353 $sql = "SELECT count(s.id) as nomarked 354 FROM {assignment_submissions} s 355 WHERE s.assignment = :assignment 356 AND s.teacher = :teacher"; 357 358 $results = $DB->get_record_sql($sql, $params); 359 360 return ($results->nomarked > 0) ? true : false; 361 } 362 363 /** 364 * Helper function to return assignment for a context module. 365 * 366 * @param object $context The context module object to return the assignment record by. 367 * @return mixed The assignment details or null record associated with the context module. 368 * @throws \dml_exception 369 */ 370 protected static function get_assignment_by_context($context) { 371 global $DB; 372 373 $params = [ 374 'modulename' => 'assignment', 375 'contextmodule' => CONTEXT_MODULE, 376 'contextid' => $context->id 377 ]; 378 379 $sql = "SELECT a.id, 380 a.name, 381 a.intro, 382 a.assignmenttype, 383 a.grade, 384 a.timedue, 385 a.timeavailable, 386 a.timemodified 387 FROM {assignment} a 388 JOIN {course_modules} cm ON a.id = cm.instance 389 JOIN {modules} m ON m.id = cm.module AND m.name = :modulename 390 JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule 391 WHERE ctx.id = :contextid"; 392 393 return $DB->get_record_sql($sql, $params); 394 } 395 396 /** 397 * Helper function to return assignment submissions submitted by / marked by a user and their contextlist. 398 * 399 * @param object $contextlist Object with the contexts related to a userid to retrieve assignment submissions by. 400 * @param int $userid The user ID to find assignment submissions that were submitted by. 401 * @param bool $teacher The teacher status to determine if marked assignment submissions should be returned. 402 * @return array Array of assignment submission details. 403 * @throws \coding_exception 404 * @throws \dml_exception 405 */ 406 protected static function get_assignment_submissions_by_contextlist($contextlist, $userid, $teacher = false) { 407 global $DB; 408 409 list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); 410 411 $params = [ 412 'contextmodule' => CONTEXT_MODULE, 413 'modulename' => 'assignment', 414 'userid' => $userid 415 ]; 416 417 $sql = "SELECT s.id as id, 418 s.assignment as assignment, 419 s.numfiles as numfiles, 420 s.data1 as data1, 421 s.data2 as data2, 422 s.grade as grade, 423 s.submissioncomment as submissioncomment, 424 s.teacher as teacher, 425 s.timemarked as timemarked, 426 s.timecreated as timecreated, 427 s.timemodified as timemodified 428 FROM {context} ctx 429 JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule 430 JOIN {modules} m ON cm.module = m.id AND m.name = :modulename 431 JOIN {assignment} a ON cm.instance = a.id 432 JOIN {assignment_submissions} s ON s.assignment = a.id 433 WHERE (s.userid = :userid"; 434 435 if ($teacher == true) { 436 $sql .= " OR s.teacher = :teacher"; 437 $params['teacher'] = $userid; 438 } 439 440 $sql .= ")"; 441 442 $sql .= " AND ctx.id {$contextsql}"; 443 $params += $contextparams; 444 445 return $DB->get_records_sql($sql, $params); 446 } 447 448 /** 449 * Helper function to retrieve assignment submissions submitted by / marked by a user for a specific assignment. 450 * 451 * @param int $assignmentid The assignment ID to retrieve assignment submissions by. 452 * @param int $userid The user ID to retrieve assignment submissions submitted / marked by. 453 * @param bool $teacher The teacher status to determine if marked assignment submissions should be returned. 454 * @return array Array of assignment submissions details. 455 * @throws \dml_exception 456 */ 457 protected static function get_assignment_submissions_by_assignment($assignmentid, $userid, $teacher = false) { 458 global $DB; 459 460 $params = [ 461 'assignment' => $assignmentid, 462 'userid' => $userid 463 ]; 464 465 $sql = "SELECT s.id as id, 466 s.assignment as assignment, 467 s.numfiles as numfiles, 468 s.data1 as data1, 469 s.data2 as data2, 470 s.grade as grade, 471 s.submissioncomment as submissioncomment, 472 s.teacher as teacher, 473 s.timemarked as timemarked, 474 s.timecreated as timecreated, 475 s.timemodified as timemodified, 476 s.userid as userid 477 FROM {assignment_submissions} s 478 WHERE s.assignment = :assignment 479 AND (s.userid = :userid"; 480 481 if ($teacher == true) { 482 $sql .= " OR s.teacher = :teacher"; 483 $params['teacher'] = $userid; 484 } 485 486 $sql .= ")"; 487 488 return $DB->get_records_sql($sql, $params); 489 } 490 491 /** 492 * Helper function generate assignment output object for exporting. 493 * 494 * @param object $assignmentdata Object containing assignment data. 495 * @return object Formatted assignment output object for exporting. 496 */ 497 protected static function get_assignment_output($assignmentdata) { 498 $assignment = (object) [ 499 'name' => $assignmentdata->name, 500 'intro' => $assignmentdata->intro, 501 'assignmenttype' => $assignmentdata->assignmenttype, 502 'grade' => $assignmentdata->grade, 503 'timemodified' => transform::datetime($assignmentdata->timemodified) 504 ]; 505 506 if ($assignmentdata->timeavailable != 0) { 507 $assignment->timeavailable = transform::datetime($assignmentdata->timeavailable); 508 } 509 510 if ($assignmentdata->timedue != 0) { 511 $assignment->timedue = transform::datetime($assignmentdata->timedue); 512 } 513 514 return $assignment; 515 } 516 517 /** 518 * Helper function generate assignment submission output object for exporting. 519 * 520 * @param object $submissiondata Object containing assignment submission data. 521 * @return object Formatted assignment submission output for exporting. 522 */ 523 protected static function get_assignment_submission_output($submissiondata) { 524 $submission = (object) [ 525 'assignment' => $submissiondata->assignment, 526 'numfiles' => $submissiondata->numfiles, 527 'data1' => $submissiondata->data1, 528 'data2' => $submissiondata->data2, 529 'grade' => $submissiondata->grade, 530 'submissioncomment' => $submissiondata->submissioncomment, 531 'teacher' => transform::user($submissiondata->teacher) 532 ]; 533 534 if ($submissiondata->timecreated != 0) { 535 $submission->timecreated = transform::datetime($submissiondata->timecreated); 536 } 537 538 if ($submissiondata->timemarked != 0) { 539 $submission->timemarked = transform::datetime($submissiondata->timemarked); 540 } 541 542 if ($submissiondata->timemodified != 0) { 543 $submission->timemodified = transform::datetime($submissiondata->timemodified); 544 } 545 546 return $submission; 547 } 548 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body