You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

549 lines
22 KiB

<?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_assignment.
*
* @package mod_assignment
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_assignment\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_privacy\local\request\helper;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\userlist;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/assignment/lib.php');
/**
* Implementation of the privacy subsystem plugin provider for mod_assignment.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider,
\core_privacy\local\request\user_preference_provider,
\core_privacy\local\request\core_userlist_provider {
/**
* Return the fields which contain personal data.
*
* @param collection $collection a reference to the collection to use to store the metadata.
* @return collection the updated collection of metadata items.
*/
public static function get_metadata(collection $collection) : collection {
$collection->add_database_table(
'assignment_submissions',
[
'userid' => 'privacy:metadata:assignment_submissions:userid',
'timecreated' => 'privacy:metadata:assignment_submissions:timecreated',
'timemodified' => 'privacy:metadata:assignment_submissions:timemodified',
'numfiles' => 'privacy:metadata:assignment_submissions:numfiles',
'data1' => 'privacy:metadata:assignment_submissions:data1',
'data2' => 'privacy:metadata:assignment_submissions:data2',
'grade' => 'privacy:metadata:assignment_submissions:grade',
'submissioncomment' => 'privacy:metadata:assignment_submissions:submissioncomment',
'teacher' => 'privacy:metadata:assignment_submissions:teacher',
'timemarked' => 'privacy:metadata:assignment_submissions:timemarked',
'mailed' => 'privacy:metadata:assignment_submissions:mailed'
],
'privacy:metadata:assignment_submissions'
);
// Legacy mod_assignment preferences from Moodle 2.X.
$collection->add_user_preference('assignment_filter', 'privacy:metadata:assignmentfilter');
$collection->add_user_preference('assignment_mailinfo', 'privacy:metadata:assignmentmailinfo');
$collection->add_user_preference('assignment_perpage', 'privacy:metadata:assignmentperpage');
$collection->add_user_preference('assignment_quickgrade', 'privacy:metadata:assignmentquickgrade');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid the userid.
* @return contextlist the list of contexts containing user info for the user.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
$contextlist = new contextlist();
$sql = "SELECT DISTINCT
ctx.id
FROM {context} ctx
JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
JOIN {assignment} a ON cm.instance = a.id
JOIN {assignment_submissions} s ON s.assignment = a.id
WHERE s.userid = :userid
OR s.teacher = :teacher";
$params = [
'contextmodule' => CONTEXT_MODULE,
'modulename' => 'assignment',
'userid' => $userid,
'teacher' => $userid
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users who have data within a 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 ($context->contextlevel != CONTEXT_MODULE) {
return;
}
$params = [
'modulename' => 'assignment',
'contextlevel' => CONTEXT_MODULE,
'contextid' => $context->id
];
$sql = "SELECT s.userid
FROM {assignment_submissions} s
JOIN {assignment} a ON s.assignment = a.id
JOIN {modules} m ON m.name = :modulename
JOIN {course_modules} cm ON a.id = cm.instance AND cm.module = m.id
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
WHERE ctx.id = :contextid
";
$userlist->add_from_sql('userid', $sql, $params);
$sql = "SELECT s.teacher
FROM {assignment_submissions} s
JOIN {assignment} a ON s.assignment = a.id
JOIN {modules} m ON m.name = :modulename
JOIN {course_modules} cm ON a.id = cm.instance AND cm.module = m.id
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
WHERE ctx.id = :contextid
";
$userlist->add_from_sql('teacher', $sql, $params);
}
/**
* Export personal data for the given approved_contextlist.
* User and context information is contained within the contextlist.
*
* @param approved_contextlist $contextlist a list of contexts approved for export.
*/
public static function export_user_data(approved_contextlist $contextlist) {
if (empty($contextlist->count())) {
return;
}
$user = $contextlist->get_user();
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel != CONTEXT_MODULE) {
continue;
}
// Cannot make use of helper::export_context_files(), need to manually export assignment details.
$assignmentdata = self::get_assignment_by_context($context);
// Get assignment details object for output.
$assignment = self::get_assignment_output($assignmentdata);
writer::with_context($context)->export_data([], $assignment);
// Check if the user has marked any assignment's submissions to determine assignment submissions to export.
$teacher = (self::has_marked_assignment_submissions($assignmentdata->id, $user->id) == true) ? true : false;
// Get the assignment submissions submitted by & marked by the user for an assignment.
$submissionsdata = self::get_assignment_submissions_by_assignment($assignmentdata->id, $user->id, $teacher);
foreach ($submissionsdata as $submissiondata) {
// Default subcontext path to export assignment submissions submitted by the user.
$subcontexts = [
get_string('privacy:submissionpath', 'mod_assignment')
];
if ($teacher == true) {
if ($submissiondata->teacher == $user->id) {
// Export assignment submissions that have been marked by the user.
$subcontexts = [
get_string('privacy:markedsubmissionspath', 'mod_assignment'),
transform::user($submissiondata->userid)
];
}
}
// Get assignment submission details object for output.
$submission = self::get_assignment_submission_output($submissiondata);
$itemid = $submissiondata->id;
writer::with_context($context)
->export_data($subcontexts, $submission)
->export_area_files($subcontexts, 'mod_assignment', 'submission', $itemid);
}
}
}
/**
* Stores the user preferences related to mod_assign.
*
* @param int $userid The user ID that we want the preferences for.
*/
public static function export_user_preferences(int $userid) {
$context = \context_system::instance();
$assignmentpreferences = [
'assignment_filter' => [
'string' => get_string('privacy:metadata:assignmentfilter', 'mod_assignment'),
'bool' => false
],
'assignment_mailinfo' => [
'string' => get_string('privacy:metadata:assignmentmailinfo', 'mod_assignment'),
'bool' => false
],
'assignment_perpage' => [
'string' => get_string('privacy:metadata:assignmentperpage', 'mod_assignment'),
'bool' => false
],
'assignment_quickgrade' => [
'string' => get_string('privacy:metadata:assignmentquickgrade', 'mod_assignment'),
'bool' => false
],
];
foreach ($assignmentpreferences as $key => $preference) {
$value = get_user_preferences($key, null, $userid);
if ($preference['bool']) {
$value = transform::yesno($value);
}
if (isset($value)) {
writer::with_context($context)
->export_user_preference('mod_assignment', $key, $value, $preference['string']);
}
}
}
/**
* Delete all data for all users in the specified context.
*
* @param \context $context the context to delete in.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if ($context->contextlevel == CONTEXT_MODULE) {
// Delete all assignment submissions for the assignment associated with the context module.
$assignment = self::get_assignment_by_context($context);
if ($assignment != null) {
$DB->delete_records('assignment_submissions', ['assignment' => $assignment->id]);
// Delete all file uploads associated with the assignment submission for the specified context.
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_assignment', 'submission');
}
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist a list of contexts approved for deletion.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
// Only retrieve assignment submissions submitted by the user for deletion.
$assignmentsubmissionids = array_keys(self::get_assignment_submissions_by_contextlist($contextlist, $userid));
$DB->delete_records_list('assignment_submissions', 'id', $assignmentsubmissionids);
// Delete all file uploads associated with the assignment submission for the user's specified list of contexts.
$fs = get_file_storage();
foreach ($contextlist->get_contextids() as $contextid) {
foreach ($assignmentsubmissionids as $submissionid) {
$fs->delete_area_files($contextid, 'mod_assignment', 'submission', $submissionid);
}
}
}
/**
* 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();
// If the context isn't for a module then return early.
if ($context->contextlevel != CONTEXT_MODULE) {
return;
}
// Fetch the assignment.
$assignment = self::get_assignment_by_context($context);
$userids = $userlist->get_userids();
list($inorequalsql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params['assignmentid'] = $assignment->id;
// Get submission ids.
$sql = "
SELECT s.id
FROM {assignment_submissions} s
JOIN {assignment} a ON s.assignment = a.id
WHERE a.id = :assignmentid
AND s.userid $inorequalsql
";
$submissionids = $DB->get_records_sql($sql, $params);
list($submissionidsql, $submissionparams) = $DB->get_in_or_equal(array_keys($submissionids), SQL_PARAMS_NAMED);
$fs = get_file_storage();
$fs->delete_area_files_select($context->id, 'mod_assignment', 'submission', $submissionidsql, $submissionparams);
// Delete related tables.
$DB->delete_records_list('assignment_submissions', 'id', array_keys($submissionids));
}
// Start of helper functions.
/**
* Helper function to check if a user has marked assignment submissions for a given assignment.
*
* @param int $assignmentid The assignment ID to check if user has marked associated submissions.
* @param int $userid The user ID to check if user has marked associated submissions.
* @return bool If user has marked associated submissions returns true, otherwise false.
* @throws \dml_exception
*/
protected static function has_marked_assignment_submissions($assignmentid, $userid) {
global $DB;
$params = [
'assignment' => $assignmentid,
'teacher' => $userid
];
$sql = "SELECT count(s.id) as nomarked
FROM {assignment_submissions} s
WHERE s.assignment = :assignment
AND s.teacher = :teacher";
$results = $DB->get_record_sql($sql, $params);
return ($results->nomarked > 0) ? true : false;
}
/**
* Helper function to return assignment for a context module.
*
* @param object $context The context module object to return the assignment record by.
* @return mixed The assignment details or null record associated with the context module.
* @throws \dml_exception
*/
protected static function get_assignment_by_context($context) {
global $DB;
$params = [
'modulename' => 'assignment',
'contextmodule' => CONTEXT_MODULE,
'contextid' => $context->id
];
$sql = "SELECT a.id,
a.name,
a.intro,
a.assignmenttype,
a.grade,
a.timedue,
a.timeavailable,
a.timemodified
FROM {assignment} a
JOIN {course_modules} cm ON a.id = cm.instance
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextmodule
WHERE ctx.id = :contextid";
return $DB->get_record_sql($sql, $params);
}
/**
* Helper function to return assignment submissions submitted by / marked by a user and their contextlist.
*
* @param object $contextlist Object with the contexts related to a userid to retrieve assignment submissions by.
* @param int $userid The user ID to find assignment submissions that were submitted by.
* @param bool $teacher The teacher status to determine if marked assignment submissions should be returned.
* @return array Array of assignment submission details.
* @throws \coding_exception
* @throws \dml_exception
*/
protected static function get_assignment_submissions_by_contextlist($contextlist, $userid, $teacher = false) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$params = [
'contextmodule' => CONTEXT_MODULE,
'modulename' => 'assignment',
'userid' => $userid
];
$sql = "SELECT s.id as id,
s.assignment as assignment,
s.numfiles as numfiles,
s.data1 as data1,
s.data2 as data2,
s.grade as grade,
s.submissioncomment as submissioncomment,
s.teacher as teacher,
s.timemarked as timemarked,
s.timecreated as timecreated,
s.timemodified as timemodified
FROM {context} ctx
JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmodule
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
JOIN {assignment} a ON cm.instance = a.id
JOIN {assignment_submissions} s ON s.assignment = a.id
WHERE (s.userid = :userid";
if ($teacher == true) {
$sql .= " OR s.teacher = :teacher";
$params['teacher'] = $userid;
}
$sql .= ")";
$sql .= " AND ctx.id {$contextsql}";
$params += $contextparams;
return $DB->get_records_sql($sql, $params);
}
/**
* Helper function to retrieve assignment submissions submitted by / marked by a user for a specific assignment.
*
* @param int $assignmentid The assignment ID to retrieve assignment submissions by.
* @param int $userid The user ID to retrieve assignment submissions submitted / marked by.
* @param bool $teacher The teacher status to determine if marked assignment submissions should be returned.
* @return array Array of assignment submissions details.
* @throws \dml_exception
*/
protected static function get_assignment_submissions_by_assignment($assignmentid, $userid, $teacher = false) {
global $DB;
$params = [
'assignment' => $assignmentid,
'userid' => $userid
];
$sql = "SELECT s.id as id,
s.assignment as assignment,
s.numfiles as numfiles,
s.data1 as data1,
s.data2 as data2,
s.grade as grade,
s.submissioncomment as submissioncomment,
s.teacher as teacher,
s.timemarked as timemarked,
s.timecreated as timecreated,
s.timemodified as timemodified,
s.userid as userid
FROM {assignment_submissions} s
WHERE s.assignment = :assignment
AND (s.userid = :userid";
if ($teacher == true) {
$sql .= " OR s.teacher = :teacher";
$params['teacher'] = $userid;
}
$sql .= ")";
return $DB->get_records_sql($sql, $params);
}
/**
* Helper function generate assignment output object for exporting.
*
* @param object $assignmentdata Object containing assignment data.
* @return object Formatted assignment output object for exporting.
*/
protected static function get_assignment_output($assignmentdata) {
$assignment = (object) [
'name' => $assignmentdata->name,
'intro' => $assignmentdata->intro,
'assignmenttype' => $assignmentdata->assignmenttype,
'grade' => $assignmentdata->grade,
'timemodified' => transform::datetime($assignmentdata->timemodified)
];
if ($assignmentdata->timeavailable != 0) {
$assignment->timeavailable = transform::datetime($assignmentdata->timeavailable);
}
if ($assignmentdata->timedue != 0) {
$assignment->timedue = transform::datetime($assignmentdata->timedue);
}
return $assignment;
}
/**
* Helper function generate assignment submission output object for exporting.
*
* @param object $submissiondata Object containing assignment submission data.
* @return object Formatted assignment submission output for exporting.
*/
protected static function get_assignment_submission_output($submissiondata) {
$submission = (object) [
'assignment' => $submissiondata->assignment,
'numfiles' => $submissiondata->numfiles,
'data1' => $submissiondata->data1,
'data2' => $submissiondata->data2,
'grade' => $submissiondata->grade,
'submissioncomment' => $submissiondata->submissioncomment,
'teacher' => transform::user($submissiondata->teacher)
];
if ($submissiondata->timecreated != 0) {
$submission->timecreated = transform::datetime($submissiondata->timecreated);
}
if ($submissiondata->timemarked != 0) {
$submission->timemarked = transform::datetime($submissiondata->timemarked);
}
if ($submissiondata->timemodified != 0) {
$submission->timemodified = transform::datetime($submissiondata->timemodified);
}
return $submission;
}
}