Skip to content

Commit

Permalink
MDL-63497 mod_glossary: Add support for removal of context users
Browse files Browse the repository at this point in the history
This issue is a part of the MDL-62560 Epic.
Also added missing ratings include and test to mod_glossary unit tests.
  • Loading branch information
mickhawkins authored and David Monllao committed Oct 22, 2018
1 parent dcdd4f1 commit 55d1ef3
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 1 deletion.
125 changes: 125 additions & 0 deletions mod/glossary/classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
namespace mod_glossary\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\deletion_criteria;
use core_privacy\local\request\helper;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;

defined('MOODLE_INTERNAL') || die();
Expand All @@ -39,6 +41,8 @@
class provider implements
// This plugin stores personal data.
\core_privacy\local\metadata\provider,
// This plugin is capable of determining which users have data within it.
\core_privacy\local\request\core_userlist_provider,
// This plugin is a core_user_data_provider.
\core_privacy\local\request\plugin\provider {

Expand Down Expand Up @@ -101,6 +105,72 @@ public static function get_contexts_for_userid(int $userid) : contextlist {
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 (!is_a($context, \context_module::class)) {
return;
}

// Find users with glossary entries.
$sql = "SELECT ge.userid
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {glossary} g ON g.id = cm.instance
JOIN {glossary_entries} ge ON ge.glossaryid = g.id
WHERE c.id = :contextid";

$params = [
'contextid' => $context->id,
'contextlevel' => CONTEXT_MODULE,
'modname' => 'glossary',
];

$userlist->add_from_sql('userid', $sql, $params);

// Find users with glossary comments.
$sql = "SELECT ge.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {glossary} g ON g.id = cm.instance
JOIN {glossary_entries} ge ON ge.glossaryid = g.id
WHERE c.id = :contextid";

$params = [
'contextid' => $context->id,
'contextlevel' => CONTEXT_MODULE,
'modname' => 'glossary',
];

\core_comment\privacy\provider::get_users_in_context_from_sql(
$userlist, 'com', 'mod_glossary', 'glossary_entry', $sql, $params);

// Find users with glossary ratings.
$sql = "SELECT ge.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {glossary} g ON g.id = cm.instance
JOIN {glossary_entries} ge ON ge.glossaryid = g.id
WHERE c.id = :contextid";

$params = [
'contextid' => $context->id,
'contextlevel' => CONTEXT_MODULE,
'modname' => 'glossary',
];

\core_rating\privacy\provider::get_users_in_context_from_sql($userlist, 'rat', 'mod_glossary', 'entry', $sql, $params);
}

/**
* Export personal data for the given approved_contextlist.
*
Expand Down Expand Up @@ -324,4 +394,59 @@ public static function delete_data_for_user(approved_contextlist $contextlist) {
}
}
}

/**
* 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();
$userids = $userlist->get_userids();
$instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
list($userinsql, $userinparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);

$glossaryentrieswhere = "glossaryid = :instanceid AND userid {$userinsql}";
$userinstanceparams = $userinparams + ['instanceid' => $instanceid];

$entriesobject = $DB->get_recordset_select('glossary_entries', $glossaryentrieswhere, $userinstanceparams, 'id', 'id');
$entries = [];

foreach ($entriesobject as $entry) {
$entries[] = $entry->id;
}

$entriesobject->close();

if (!$entries) {
return;
}

list($insql, $inparams) = $DB->get_in_or_equal($entries, SQL_PARAMS_NAMED);

// Delete related entry aliases.
$DB->delete_records_list('glossary_alias', 'entryid', $entries);

// Delete related entry categories.
$DB->delete_records_list('glossary_entries_categories', 'entryid', $entries);

// Delete related entry and attachment files.
get_file_storage()->delete_area_files_select($context->id, 'mod_glossary', 'entry', $insql, $inparams);
get_file_storage()->delete_area_files_select($context->id, 'mod_glossary', 'attachment', $insql, $inparams);

// Delete user tags related to this glossary.
\core_tag\privacy\provider::delete_item_tags_select($context, 'mod_glossary', 'glossary_entries', $insql, $inparams);

// Delete related ratings.
\core_rating\privacy\provider::delete_ratings_select($context, 'mod_glossary', 'entry', $insql, $inparams);

// Delete comments.
\core_comment\privacy\provider::delete_comments_for_users($userlist, 'mod_glossary', 'glossary_entry');

// Now delete all user related entries.
$deletewhere = "glossaryid = :instanceid AND userid {$userinsql}";
$DB->delete_records_select('glossary_entries', $deletewhere, $userinstanceparams);
}
}
147 changes: 146 additions & 1 deletion mod/glossary/tests/privacy_provider_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
require_once($CFG->dirroot . '/rating/lib.php');

/**
* Privacy provider tests class.
Expand Down Expand Up @@ -131,6 +132,27 @@ public function test_get_contexts_for_userid() {
$this->assertEquals($cmcontext->id, $contextforuser->id);
}

/**
* Test for provider::get_users_in_context().
*/
public function test_get_users_in_context() {
$component = 'mod_glossary';
$cm = get_coursemodule_from_instance('glossary', $this->glossary->id);
$cmcontext = context_module::instance($cm->id);

$userlist = new \core_privacy\local\request\userlist($cmcontext, $component);
provider::get_users_in_context($userlist);

$this->assertCount(1, $userlist);

$expected = [$this->student->id];
$actual = $userlist->get_userids();
sort($expected);
sort($actual);

$this->assertEquals($expected, $actual);
}

/**
* Test for provider::export_user_data().
*/
Expand Down Expand Up @@ -212,6 +234,7 @@ public function test_delete_data_for_user() {
global $DB;
$generator = $this->getDataGenerator();

// Create another student who will add an entry to the first glossary.
$student2 = $generator->create_user();
$generator->enrol_user($student2->id, $this->course->id, 'student');

Expand All @@ -235,6 +258,11 @@ public function test_delete_data_for_user() {

core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge3->id, $context1, ['Pizza', 'Noodles']);

// As a teacher, rate student 2's entry.
$this->setUser($this->teacher);
$rating = $this->get_rating_object($context1, $ge3->id);
$rating->update_rating(2);

// Before deletion, we should have 3 entries, one rating and 2 tag instances.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
$this->assertEquals(3, $count);
Expand All @@ -243,7 +271,10 @@ public function test_delete_data_for_user() {
$this->assertEquals(2, $tagcount);
$aliascount = $DB->count_records('glossary_alias', ['entryid' => $ge3->id]);
$this->assertEquals(1, $aliascount);
// Create another student who will add an entry to the first glossary.
$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(1, $ratingcount);

$contextlist = new \core_privacy\local\request\approved_contextlist($student2, 'glossary',
[$context1->id, $context2->id]);
provider::delete_data_for_user($contextlist);
Expand Down Expand Up @@ -274,6 +305,120 @@ public function test_delete_data_for_user() {
$commentcount = $DB->count_records('comments', ['component' => 'mod_glossary', 'commentarea' => 'glossary_entry',
'userid' => $this->student->id]);
$this->assertEquals(1, $commentcount);

$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(0, $ratingcount);
}

/**
* Test for provider::delete_data_for_users().
*/
public function test_delete_data_for_users() {
global $DB;
$generator = $this->getDataGenerator();

$student2 = $generator->create_user();
$generator->enrol_user($student2->id, $this->course->id, 'student');

$cm1 = get_coursemodule_from_instance('glossary', $this->glossary->id);
$glossary2 = $this->plugingenerator->create_instance(['course' => $this->course->id]);
$cm2 = get_coursemodule_from_instance('glossary', $glossary2->id);

$ge1 = $this->plugingenerator->create_content($this->glossary, ['concept' => 'first user glossary entry', 'approved' => 1]);
$ge2 = $this->plugingenerator->create_content($glossary2, ['concept' => 'first user second glossary entry',
'approved' => 1], ['two']);

$context1 = context_module::instance($cm1->id);
$context2 = context_module::instance($cm2->id);
core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge1->id, $context1, ['Parmi', 'Sushi']);

$this->setUser($student2);
$ge3 = $this->plugingenerator->create_content($this->glossary, ['concept' => 'second user glossary entry',
'approved' => 1], ['three']);

$comment = $this->get_comment_object($context1, $ge3->id);
$comment->add('User 2 comment 1');
$comment = $this->get_comment_object($context2, $ge2->id);
$comment->add('User 2 comment 2');

core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge3->id, $context1, ['Pizza', 'Noodles']);
core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $ge2->id, $context2, ['Potato', 'Kumara']);

// As a teacher, rate student 2's entry.
$this->setUser($this->teacher);
$rating = $this->get_rating_object($context1, $ge3->id);
$rating->update_rating(2);

// Check correct glossary 1 record counts before deletion.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
// Note: There is an additional student entry from setUp().
$this->assertEquals(3, $count);

list($context1itemsql, $context1itemparams) = $DB->get_in_or_equal([$ge1->id, $ge3->id], SQL_PARAMS_NAMED);
$geparams = [
'component' => 'mod_glossary',
'itemtype' => 'glossary_entries',
];
$geparams += $context1itemparams;
$wheresql = "component = :component AND itemtype = :itemtype AND itemid {$context1itemsql}";

$tagcount = $DB->count_records_select('tag_instance', $wheresql, $geparams);
$this->assertEquals(4, $tagcount);

$aliascount = $DB->count_records_select('glossary_alias', "entryid {$context1itemsql}", $context1itemparams);
$this->assertEquals(1, $aliascount);

$commentparams = [
'component' => 'mod_glossary',
'commentarea' => 'glossary_entry',
];
$commentparams += $context1itemparams;
$commentwhere = "component = :component AND commentarea = :commentarea AND itemid {$context1itemsql}";

$commentcount = $DB->count_records_select('comments', $commentwhere, $commentparams);
$this->assertEquals(1, $commentcount);

$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(1, $ratingcount);

// Perform deletion within context 1 for both students.
$approveduserlist = new core_privacy\local\request\approved_userlist($context1, 'mod_glossary',
[$this->student->id, $student2->id]);
provider::delete_data_for_users($approveduserlist);

// After deletion, all context 1 entries, tags and comment should be deleted.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $this->glossary->id]);
$this->assertEquals(0, $count);

$tagcount = $DB->count_records_select('tag_instance', $wheresql, $geparams);
$this->assertEquals(0, $tagcount);

$aliascount = $DB->count_records_select('glossary_alias', "entryid {$context1itemsql}", $context1itemparams);
$this->assertEquals(0, $aliascount);

$commentcount = $DB->count_records_select('comments', $commentwhere, $commentparams);
$this->assertEquals(0, $commentcount);

// Context 2 entries should remain intact.
$count = $DB->count_records('glossary_entries', ['glossaryid' => $glossary2->id]);
$this->assertEquals(1, $count);

$tagcount = $DB->count_records('tag_instance', ['component' => 'mod_glossary', 'itemtype' => 'glossary_entries',
'itemid' => $ge2->id]);
$this->assertEquals(2, $tagcount);

$aliascount = $DB->count_records('glossary_alias', ['entryid' => $ge2->id]);
$this->assertEquals(1, $aliascount);

$commentcount = $DB->count_records('comments', ['component' => 'mod_glossary', 'commentarea' => 'glossary_entry',
'itemid' => $ge2->id]);
$this->assertEquals(1, $commentcount);

$ratingcount = $DB->count_records('rating', ['component' => 'mod_glossary', 'ratingarea' => 'entry',
'itemid' => $ge3->id]);
$this->assertEquals(0, $ratingcount);
}

/**
Expand Down

0 comments on commit 55d1ef3

Please sign in to comment.