Select Git revision
-
Tobias Reischmann authoredTobias Reischmann authored
provider.php 25.49 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 class for requesting user data.
*
* @package mod_pdfannotator
* @category privacy
* @copyright 2018 RWTH Aachen (see README.md)
* @author Anna Heynkes
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_pdfannotator\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\writer;
use \core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\userlist;
/**
* Description of provider
*
* @author Admin
*/
class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider {
/**
* This function implements the \core_privacy\local\metadata\provider interface.
*
* It describes what kind of data is stored by the pdfannotator, including:
*
* 1. Items stored in a Moodle subsystem - for example files, and ratings
* 2. Items stored in the Moodle database
* 3. User preferences stored site-wide within Moodle for the pdfannotator
* 4. Data being exported to an external location
*
* @param collection $collection
* @return collection
*/
public static function get_metadata(collection $collection): collection {
// 1. Indicating that you store content in a Moodle subsystem.
// 1.1 Files uploaded by users are saved.
$collection->add_subsystem_link(
'core_files', [], 'privacy:metadata:core_files'
);
// 2. Describing data stored in database tables.
// 2.1 A user's annotations in the pdf are stored.
$collection->add_database_table(
'pdfannotator_annotations', [
'userid' => 'privacy:metadata:pdfannotator_annotations:userid',
'id' => 'privacy:metadata:pdfannotator_annotations:annotationid',
], 'privacy:metadata:pdfannotator_annotations'
);
// 2.2 A user's comments are stored.
$collection->add_database_table(
'pdfannotator_comments', [
'userid' => 'privacy:metadata:pdfannotator_comments:userid',
'annotationid' => 'privacy:metadata:pdfannotator_comments:annotationid',
'content' => 'privacy:metadata:pdfannotator_comments:content',
], 'privacy:metadata:pdfannotator_comments'
);
// 2.3 Users can report other users' comments as inappropriate. These reports stored.
$collection->add_database_table(
'pdfannotator_reports', [
'commentid' => 'privacy:metadata:pdfannotator_reports:commentid',
'message' => 'privacy:metadata:pdfannotator_reports:message',
'userid' => 'privacy:metadata:pdfannotator_reports:userid',
], 'privacy:metadata:pdfannotator_reports'
);
// 2.4 A user's subscriptions are stored.
$collection->add_database_table(
'pdfannotator_subscriptions', [
'annotationid' => 'privacy:metadata:pdfannotator_subscriptions:annotationid',
'userid' => 'privacy:metadata:pdfannotator_subscriptions:userid',
], 'privacy:metadata:pdfannotator_subscriptions'
);
// 2.5 Votes are stored.
$collection->add_database_table(
'pdfannotator_votes', [
'commentid' => 'privacy:metadata:pdfannotator_votes:commentid',
'userid' => 'privacy:metadata:pdfannotator_votes:userid',
], 'privacy:metadata:pdfannotator_votes'
);
// 3. There are no site-wide user preferences stored at present.
// 4. No data is exported to an external location at present.
return $collection;
}
/**
* This function implements the core_privacy\local\request\plugin\provider interface.
* It retursn a list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist 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' => 'pdfannotator',
'contextlevel' => CONTEXT_MODULE,
'userid1' => $userid,
'userid2' => $userid,
'userid3' => $userid,
'userid5' => $userid,
'userid6' => $userid,
];
$sql = "SELECT DISTINCT c.id
FROM {context} c
INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
INNER JOIN {pdfannotator} p ON p.id = cm.instance
LEFT JOIN {pdfannotator_annotations} a ON a.pdfannotatorid = p.id
LEFT JOIN {pdfannotator_subscriptions} s ON s.annotationid = a.id
LEFT JOIN {pdfannotator_comments} k ON k.annotationid = a.id
LEFT JOIN {pdfannotator_reports} r ON r.commentid = k.id
LEFT JOIN {pdfannotator_votes} v ON v.commentid = k.id
WHERE (
a.userid = :userid1 OR
s.userid = :userid2 OR
k.userid = :userid3 OR
r.userid = :userid5 OR
v.userid = :userid6
)
";
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Export all user data for the specified user, in the specified contexts, using the supplied exporter instance.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/pdfannotator/locallib.php');
if (empty($contextlist)) {
return;
}
$userid = $contextlist->get_user()->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$sql = "SELECT
c.id AS contextid,
cm.id AS cmid,
p.id AS id,
p.name AS pdfannotatorname
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid
JOIN {pdfannotator} p ON p.id = cm.instance
WHERE (
c.id {$contextsql}
)
";
// Keep a mapping of pdfannotatorid to contextid.
$mappings = [];
$pdfannotators = $DB->get_recordset_sql($sql, $contextparams);
foreach ($pdfannotators as $pdfannotator) {
$mappings[$pdfannotator->id] = $pdfannotator->contextid;
$context = \context::instance_by_id($mappings[$pdfannotator->id]);
// Get all questions asked by this user.
$sql1 = "SELECT c.content, c.timecreated, c.visibility
FROM {pdfannotator_comments} c
WHERE c.isquestion = 1 AND c.userid = :userid AND c.pdfannotatorid = :pdfannotator";
$myquestions = $DB->get_records_sql($sql1, array('userid' => $userid, 'pdfannotator' => $pdfannotator->id));
foreach ($myquestions as $myquestion) {
$myquestion->timecreated = pdfannotator_get_user_datetime($myquestion->timecreated);
}
// Get all non-question comments by this user, together with the question.
$sql2 = "SELECT c.content, c.timecreated, c.visibility, q.content as questioncontent "
. "FROM {pdfannotator_comments} c "
. "JOIN {pdfannotator_annotations} a ON c.annotationid = a.id "
. "JOIN {pdfannotator_comments} q ON q.annotationid = c.annotationid "
. "WHERE q.isquestion = :question AND c.isquestion = :normalcomment AND c.userid = :userid AND a.pdfannotatorid = :pdfannotator";
$mycomments = $DB->get_records_sql($sql2, array('question' => 1, 'normalcomment' => 0, 'userid' => $userid, 'pdfannotator' => $pdfannotator->id));
foreach ($mycomments as $mycomment) {
$mycomment->timecreated = pdfannotator_get_user_datetime($mycomment->timecreated);
}
// Get all subscriptions of this user (exluding their own questions which they're automatically subscribed to).
$sql3 = "SELECT c.content
FROM {pdfannotator_subscriptions} s JOIN {pdfannotator_annotations} a ON s.annotationid = a.id JOIN {pdfannotator_comments} c ON c.annotationid = a.id
WHERE c.isquestion = 1 AND s.userid = :userid AND a.pdfannotatorid = :pdfannotator AND NOT a.userid = :u";
$mysubscriptions = $DB->get_records_sql($sql3, array('userid' => $userid, 'pdfannotator' => $pdfannotator->id, 'u' => $userid));
// Get all comments this user voted for in this annotator.
$sql4 = "SELECT c.content
FROM {pdfannotator_comments} c JOIN {pdfannotator_votes} v on v.commentid = c.id
WHERE v.userid = :userid AND c.pdfannotatorid = :pdfannotator";
$myvotes = $DB->get_records_sql($sql4, array('userid' => $userid, 'pdfannotator' => $pdfannotator->id));
// Get all reports this user wrote.
$sql6 = "SELECT r.message
FROM {pdfannotator_reports} r JOIN {pdfannotator_comments} c ON c.id = r.commentid
WHERE r.userid = :userid AND r.pdfannotatorid = :pdfannotator";
$myreportmessages = $DB->get_records_sql($sql6, array('userid' => $userid, 'pdfannotator' => $pdfannotator->id));
// Get all drawings and textboxes this user made in this annotator.
$sql7 = "SELECT a.data, a.timecreated
FROM {pdfannotator_annotations} a JOIN {pdfannotator_annotationtypes} t ON a.annotationtypeid = t.id
WHERE t.name IN (:type1, :type2) AND a.userid = :userid AND a.pdfannotatorid = :pdfannotator";
$mydrawingsandtextboxes = $DB->get_records_sql($sql7, array('type1' => 'drawing', 'type2' => 'textbox', 'userid' => $userid, 'pdfannotator' => $pdfannotator->id));
foreach ($mydrawingsandtextboxes as $mydrawingortextbox) {
$mydrawingortextbox->timecreated = pdfannotator_get_user_datetime($mydrawingortextbox->timecreated);
}
$pdfannotator->myquestions = $myquestions;
$pdfannotator->mycomments = $mycomments;
$pdfannotator->mysubscriptions = $mysubscriptions;
$pdfannotator->myvotes = $myvotes;
$pdfannotator->myreportmessages = $myreportmessages;
$pdfannotator->mydrawingsandtextboxes = $mydrawingsandtextboxes;
writer::with_context($context)->export_data([], $pdfannotator);
}
$pdfannotators->close();
}
/**
* Delete all personal data for all users in the specified context.
*
* @param context $context Context to delete data from.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if ($context->contextlevel != CONTEXT_MODULE) {
return;
}
$instanceid = $context->instanceid;
$cm = get_coursemodule_from_id('pdfannotator', $instanceid);
if (!$cm) {
return;
}
// 1. Delete all reports of comments in this annotator.
$DB->delete_records('pdfannotator_reports', ['pdfannotatorid' => $instanceid]);
// 2. Delete all votes in this annotator.
$sql = "SELECT v.id FROM {pdfannotator_votes} v WHERE v.commentid IN "
. "(SELECT c.id FROM {pdfannotator_comments} c "
. "JOIN {pdfannotator_annotations} a ON c.annotationid = a.id WHERE a.pdfannotatorid = ?)";
$votes = $DB->get_records_sql($sql, array($instanceid));
foreach ($votes as $vote) {
$DB->delete_records('pdfannotator_votes', array("id" => $vote->id));
}
// 3. Delete all subscriptions in this annotator.
$sql = "SELECT s.id FROM {pdfannotator_subscriptions} s "
. "WHERE s.annotationid IN (SELECT a.id FROM {pdfannotator_annotations} a WHERE a.pdfannotatorid = ?)";
$subscriptions = $DB->get_records_sql($sql, array($instanceid));
foreach ($subscriptions as $subscription) {
$DB->delete_records('pdfannotator_subscriptions', array("id" => $subscription->id));
}
// 4. Delete all comments in this annotator.
$sql = "SELECT c.id FROM {pdfannotator_comments} c WHERE c.annotationid IN (SELECT a.id FROM {pdfannotator_annotations} a WHERE a.pdfannotatorid = ?)";
$comments = $DB->get_records_sql($sql, array($instanceid));
foreach ($comments as $comment) {
$DB->delete_records('pdfannotator_comments', array("id" => $comment->id));
}
// 5. Delete all annotations in this annotator.
$annotations = $DB->get_fieldset_select('pdfannotator_annotations', 'id', "pdfannotatorid = ?", array($instanceid));
foreach ($annotations as $annotationid) {
$DB->delete_records('pdfannotator_annotations', array("id" => $annotationid));
}
}
/**
*
* Delete personal data for the user in a list of contexts.
*
* @param \mod_pdfannotator\privacy\approved_contextlist $contextlist List of contexts to delete data from.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
$instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
// 1. Delete all reports this user made in this annotator.
$DB->delete_records(
'pdfannotator_reports',
['pdfannotatorid' => $instanceid, 'userid' => $userid]
);
// 2. Delete all votes this user made in this annotator.
$sql = "SELECT v.id
FROM {pdfannotator_votes} v
WHERE v.userid = ? AND v.commentid IN
(SELECT c.id
FROM {pdfannotator_comments} c
WHERE c.pdfannotatorid = ?)";
$votes = $DB->get_records_sql($sql , array($userid, $instanceid));
foreach ($votes as $vote) {
$DB->delete_records('pdfannotator_votes', array("id" => $vote->id));
}
// 3. Delete all subscriptions this user made in this annotator.
$sql = "SELECT s.id
FROM {pdfannotator_subscriptions} s
WHERE s.userid = ? AND s.annotationid IN
(SELECT a.id
FROM {pdfannotator_annotations} a
WHERE a.pdfannotatorid = ?)";
$subscriptions = $DB->get_records_sql($sql, array($userid, $instanceid));
foreach ($subscriptions as $subscription) {
$DB->delete_records('pdfannotator_subscriptions', array("id" => $subscription->id));
}
// 4. Select all comments this user made in this annotator.
$sql = "SELECT c.*
FROM {pdfannotator_comments} c
WHERE c.pdfannotatorid = ? AND c.userid = ?";
$comments = $DB->get_records_sql($sql, array($instanceid, $userid));
foreach ($comments as $comment) {
// Delete question comments, their underlying annotation as well as all answers and subscriptions.
if ($comment->isquestion) {
self::delete_annotation($comment->annotationid);
continue;
}
// Empty or delete all other comments.
self::empty_or_delete_comment($comment);
}
// 5. Select the IDs of all annotations that were made by this user in this annotator. Then call the function to delete the annotation and any adjacent comments.
$annotations = $DB->get_fieldset_select('pdfannotator_annotations', 'id', "pdfannotatorid = ? AND userid = ?", array($instanceid, $userid));
foreach ($annotations as $annotationid) {
self::delete_annotation($annotationid);
}
}
}
// Status quo:
// Deleting the initial or final comment of a 'thread' will remove it from the comments table.
// Deleting any other comment will merely set the field isdeleted of the comments table to 1, so that the comment will be displayed as deleted within the 'thread'.
/**
* Function deletes an annotation and all comments and subscriptions attached to it.
*
* @param int $annotationid One annotationid (int) to delete
*/
public static function delete_annotation($annotationid) {
global $DB;
// 1. Get all comments on this annotation and prepare them for deletion.
// 1.1 Retrieve comments from DB.
$comments = $DB->get_records('pdfannotator_comments', array("annotationid" => $annotationid));
foreach ($comments as $comment) {
// 1.2 Delete any votes for these comments.
$DB->delete_records('pdfannotator_votes', array("commentid" => $comment->id));
// Delete any pictures of the comment.
$DB->delete_records('files', array("component" => "mod_pdfannotator", "filearea" => "post", "itemid" => $comment->id));
}
// 1.3 Now delete all comments.
$DB->delete_records('pdfannotator_comments', array("annotationid" => $annotationid));
// 2. Delete subscriptions to the question.
$DB->delete_records('pdfannotator_subscriptions', array('annotationid' => $annotationid));
// 3. Delete the annotation itself.
$DB->delete_records('pdfannotator_annotations', array("id" => $annotationid));
}
/**
* Function empties or deletes a comment.
*
* @param \mod_pdfannotator\output\comment $comment comment to be emptied or deleted
*
* @throws \dml_exception
*/
public static function empty_or_delete_comment($comment) {
global $DB;
$select = "annotationid = ? AND timecreated > ? AND isdeleted = ?";
$wasanswered = $DB->record_exists_select('pdfannotator_comments', $select, array($comment->annotationid, $comment->timecreated, 0));
// If the comment was answered, empty it and mark it as deleted for a special display.
if ($wasanswered) {
$DB->update_record('pdfannotator_comments', array("id" => $comment->id, "content" => "", "isdeleted" => 1));
// If not, just delete it.
} else {
// But first: Check if the predecessor was already marked as deleted, too and if so, delete it completely.
$sql = "SELECT id, isdeleted from {pdfannotator_comments} WHERE annotationid = ? AND isquestion = ? AND timecreated < ? ORDER BY id DESC";
$params = array($comment->annotationid, 0, $comment->timecreated);
$predecessors = $DB->get_records_sql($sql, $params);
foreach ($predecessors as $predecessor) {
if ($predecessor->isdeleted) {
$DB->delete_records('pdfannotator_comments', array("id" => $predecessor->id));
} else {
break;
}
}
// Now delete the selected comment.
$DB->delete_records('pdfannotator_comments', array("id" => $comment->id));
}
}
/**
* Finds the users in the given userlists's context.
* @param userlist $userlist
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_module) {
return;
}
$params = [
'contextid' => $context->instanceid,
'modulename' => 'pdfannotator',
];
// Comments.
$sql = "SELECT ac.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {pdfannotator} a ON a.id = cm.instance
JOIN {pdfannotator_comments} ac ON ac.pdfannotatorid = a.id
WHERE cm.id = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
// Reports.
$sql = "SELECT ar.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {pdfannotator} a ON a.id = cm.instance
JOIN {pdfannotator_reports} ar ON ar.pdfannotatorid = a.id
WHERE cm.id = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
// Annotations.
$sql = "SELECT ats.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {pdfannotator} a ON a.id = cm.instance
JOIN {pdfannotator_annotations} ats ON ats.pdfannotatorid = a.id
WHERE cm.id = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
// Votes.
$sql = "SELECT v.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {pdfannotator} a ON a.id = cm.instance
JOIN {pdfannotator_comments} ac ON ac.pdfannotatorid = a.id
JOIN {pdfannotator_votes} v ON v.commentid = ac.id
WHERE cm.id = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
// Subscriptions
$sql = "SELECT asub.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
JOIN {pdfannotator} a ON a.id = cm.instance
JOIN {pdfannotator_annotations} ats ON ats.pdfannotatorid = a.id
JOIN {pdfannotator_subscriptions} asub ON a.id = asub.annotationid
WHERE cm.id = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Deletes data for users in given userlist's context.
* @param approved_userlist $userlist
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
if (!$context instanceof \context_module) {
return;
}
list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
// Find the instance.
$annotationinstance = get_coursemodule_from_id('pdfannotator', $context->instanceid);
$annotatorid = $annotationinstance->id;
// Combine instance + user sql.
$params = array_merge(['pdfannotatorid' => $annotatorid], $userinparams);
$sql = "pdfannotatorid = :pdfannotatorid AND userid {$userinsql}";
// Delete subscriptions.
$annotations = $DB->get_records('pdfannotator_annotations', ['pdfannotatorid' => $annotatorid]);
$annotationids = array_column($annotations, 'id');
list($subinsql, $subinparams) = $DB->get_in_or_equal($annotationids, SQL_PARAMS_NAMED);
$DB->execute("DELETE FROM {pdfannotator_subscriptions} sub
WHERE sub.userid {$userinsql}
AND sub.annotationid {$subinsql}",
array_merge($userinparams, $subinparams));
// Delete votes.
$comments = $DB->get_records('pdfannotator_comments', ['pdfannotatorid' => $annotatorid]);
$commentsids = array_column($comments, 'id');
list($commentinsql, $commentinparams) = $DB->get_in_or_equal($commentsids, SQL_PARAMS_NAMED);
$DB->execute("DELETE FORM {pdfannotator_votes} votes
WHERE vote.userid {$userinsql}
AND vote.commentid {$commentinsql}",
array_merge($userinparams, $commentinparams));
// Delete rest of data.
$DB->delete_records_select('pdfannotator_annotations', $sql, $params);
$DB->delete_records_select('pdfannotator_reports', $sql, $params);
$DB->delete_records_select('pdfannotator_comments', $sql, $params);
// Delete pictures in comments.
$DB->execute("DELETE FORM {files} imgs
WHERE imgs.component = 'mod_pdfannotator'
AND imgs.filearea = 'post'
AND imgs.userid {$userinsql}
AND imgs.itemid {$commentinsql}",
array_merge($userinparams, $commentinparams));
}
}