Merge branch 'MDL-63666-master' of git://github.com/mihailges/moodle

This commit is contained in:
Andrew Nicols 2018-11-02 10:46:33 +08:00
commit 91071ad785
9 changed files with 498 additions and 7 deletions

View File

@ -0,0 +1,57 @@
<?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/>.
/**
* Logstore userlist provider interface.
*
* @package tool_log
* @copyright 2018 Adrian Greeve
* @author Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_log\local\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Logstore userlist provider interface.
*
* Logstore subplugins providers must implement this interface.
*
* @package tool_log
* @copyright 2018 Adrian Greeve
* @author Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface logstore_userlist_provider extends \core_privacy\local\request\plugin\subplugin_provider {
/**
* Add user IDs that contain user information for the specified context.
*
* @param \core_privacy\local\request\userlist $userlist The userlist to add the users to.
* @return void
*/
public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist);
/**
* Delete all data for a list of users in the specified context.
*
* @param \core_privacy\local\request\approved_userlist $userlist The specific context and users to delete data for.
* @return void
*/
public static function delete_data_for_userlist(\core_privacy\local\request\approved_userlist $userlist);
}

View File

@ -120,4 +120,19 @@ trait moodle_database_export_and_delete {
$db->delete_records_select($table, "userid = :userid AND contextid $insql", $params);
}
/**
* Delete all user data for the specified users, in the specified context.
*
* @param \core_privacy\local\request\approved_userlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_userlist(\core_privacy\local\request\approved_userlist $userlist) {
list($db, $table) = static::get_database_and_table();
if (!$db || !$table) {
return;
}
list($insql, $inparams) = $db->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$params = array_merge($inparams, ['contextid' => $userlist->get_context()->id]);
$db->delete_records_select($table, "contextid = :contextid AND userid $insql", $params);
}
}

View File

@ -43,7 +43,8 @@ use tool_log\log\manager;
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\provider {
\core_privacy\local\request\subsystem\provider,
\core_privacy\local\request\core_userlist_provider {
/**
* Returns metadata.
@ -68,6 +69,17 @@ class provider implements
return $contextlist;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param \core_privacy\local\request\userlist $userlist The userlist containing the list of users who have data in
* this context/plugin combination.
*/
public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
$interface = \tool_log\local\privacy\logstore_userlist_provider::class;
static::call_subplugins_method_with_args('add_userids_for_context', [$userlist], $interface);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
@ -97,16 +109,29 @@ class provider implements
static::call_subplugins_method_with_args('delete_data_for_user', [$contextlist]);
}
/**
* Delete multiple users within a single context.
*
* @param \core_privacy\local\request\approved_userlist $userlist The approved context and user information to delete
* information for.
*/
public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
$interface = \tool_log\local\privacy\logstore_userlist_provider::class;
static::call_subplugins_method_with_args('delete_data_for_userlist', [$userlist], $interface);
}
/**
* Invoke the subplugins method with arguments.
*
* @param string $method The method name.
* @param array $args The arguments.
* @param string $interface The interface to use. By default uses the logstore_provider.
* @return void
*/
protected static function call_subplugins_method_with_args($method, array $args = []) {
$interface = \tool_log\local\privacy\logstore_provider::class;
protected static function call_subplugins_method_with_args($method, array $args = [], string $interface = null) {
if (!isset($interface)) {
$interface = \tool_log\local\privacy\logstore_provider::class;
}
\core_privacy\manager::plugintype_class_callback('logstore', $interface, $method, $args);
}
}

View File

@ -40,7 +40,8 @@ use core_privacy\local\request\contextlist;
*/
class provider implements
\core_privacy\local\metadata\provider,
\tool_log\local\privacy\logstore_provider {
\tool_log\local\privacy\logstore_provider,
\tool_log\local\privacy\logstore_userlist_provider {
use \tool_log\local\privacy\moodle_database_export_and_delete;
@ -91,6 +92,38 @@ class provider implements
$contextlist->add_from_sql($sql, []);
}
/**
* Add user IDs that contain user information for the specified context.
*
* @param \core_privacy\local\request\userlist $userlist The userlist to add the users to.
* @return void
*/
public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) {
list($db, $table) = static::get_database_and_table();
if (!$db || !$table) {
return;
}
$userids = [];
$records = $db->get_records($table, ['contextid' => $userlist->get_context()->id], '',
'id, userid, relateduserid, realuserid');
if (empty($records)) {
return;
}
foreach ($records as $record) {
$userids[] = $record->userid;
if (!empty($record->relateduserid)) {
$userids[] = $record->relateduserid;
}
if (!empty($record->realuserid)) {
$userids[] = $record->realuserid;
}
}
$userids = array_unique($userids);
$userlist->add_users($userids);
}
/**
* Get the database object.
*

View File

@ -151,6 +151,55 @@ class logstore_database_privacy_testcase extends provider_testcase {
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
}
/**
* Check that user IDs are returned for a given context.
*/
public function test_add_userids_for_context() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$u4 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$this->enable_logging();
$manager = get_log_manager(true);
$userlist = new \core_privacy\local\request\userlist($sysctx, 'logstore_database');
$userids = $userlist->get_userids();
$this->assertEmpty($userids);
provider::add_userids_for_context($userlist);
$userids = $userlist->get_userids();
$this->assertEmpty($userids);
// User one should be added (userid).
$this->setUser($u1);
$e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
// User two (userids) and three (relateduserid) should be added.
$this->setUser($u2);
$e = \logstore_database\event\unittest_executed::create(['context' => $sysctx, 'relateduserid' => $u3->id]);
$e->trigger();
// The admin user should be added (realuserid).
$this->setAdminUser();
\core\session\manager::loginas($u2->id, context_system::instance());
$e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
// Set off an event in a different context. User 4 should not be returned below.
$this->setUser($u4);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
provider::add_userids_for_context($userlist);
$userids = $userlist->get_userids();
$this->assertCount(4, $userids);
$expectedresult = [$admin->id, $u1->id, $u2->id, $u3->id];
$this->assertEmpty(array_diff($expectedresult, $userids));
}
public function test_delete_data_for_user() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
@ -233,6 +282,51 @@ class logstore_database_privacy_testcase extends provider_testcase {
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
/**
* Check that data is removed for the listed users in a given context.
*/
public function test_delete_data_for_userlist() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$u4 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($course->id);
$this->enable_logging();
$manager = get_log_manager(true);
$this->setUser($u1);
$e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
$this->setUser($u2);
$e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
$this->setUser($u3);
$e = \logstore_database\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
$this->setUser($u4);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
// Check that four records were created.
$this->assertEquals(4, $DB->count_records('logstore_standard_log'));
$userlist = new \core_privacy\local\request\approved_userlist($sysctx, 'logstore_database', [$u1->id, $u3->id]);
provider::delete_data_for_userlist($userlist);
// We should have a record for u2 and u4.
$this->assertEquals(2, $DB->count_records('logstore_standard_log'));
$records = $DB->get_records('logstore_standard_log', ['contextid' => $sysctx->id]);
$this->assertCount(1, $records);
$currentrecord = array_shift($records);
$this->assertEquals($u2->id, $currentrecord->userid);
}
public function test_export_data_for_user() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();

View File

@ -44,7 +44,8 @@ use tool_log\local\privacy\helper;
*/
class provider implements
\core_privacy\local\metadata\provider,
\tool_log\local\privacy\logstore_provider {
\tool_log\local\privacy\logstore_provider,
\tool_log\local\privacy\logstore_userlist_provider {
/**
* Returns metadata.
@ -89,6 +90,22 @@ class provider implements
$contextlist->add_from_sql($sql, $params);
}
/**
* Add user IDs that contain user information for the specified context.
*
* @param \core_privacy\local\request\userlist $userlist The userlist to add the users to.
* @return void
*/
public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) {
$context = $userlist->get_context();
list($insql, $params) = static::get_sql_where_from_contexts([$context]);
$sql = "SELECT l.userid
FROM {log} l
WHERE $insql";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
@ -168,6 +185,24 @@ class provider implements
$DB->delete_records_select('log', "$sql AND userid = :userid", array_merge($params, ['userid' => $userid]));
}
/**
* Delete all data for a list of users in the specified context.
*
* @param \core_privacy\local\request\approved_userlist $userlist The specific context and users to delete data for.
* @return void
*/
public static function delete_data_for_userlist(\core_privacy\local\request\approved_userlist $userlist) {
global $DB;
list($sql, $params) = static::get_sql_where_from_contexts([$userlist->get_context()]);
if (empty($sql)) {
return;
}
list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$params = array_merge($params, $userparams);
$DB->delete_records_select('log', "$sql AND userid $usersql", $params);
}
/**
* Get an SQL where statement from a list of contexts.
*

View File

@ -87,6 +87,71 @@ class logstore_legacy_privacy_testcase extends provider_testcase {
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx]);
}
/**
* Test returning user IDs for a given context.
*/
public function test_add_userids_for_context() {
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$module = $this->getDataGenerator()->create_module('url', ['course' => $course]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($course->id);
$cm1ctx = context_module::instance($module->cmid);
$userctx = context_user::instance($u1->id);
$this->enable_logging();
$manager = get_log_manager(true);
$this->setUser($u1);
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 1]]);
$e->trigger();
$this->setUser($u2);
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 2]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]);
$e->trigger();
$this->setUser($u3);
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 4]]);
$e->trigger();
$this->setUser($u1);
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 5]]);
$e->trigger();
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 6]]);
$e->trigger();
$this->setUser($u2);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 7]]);
$e->trigger();
$this->setUser($u3);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 8]]);
$e->trigger();
// Start with system and check that each of the contexts returns what we expected.
$userlist = new \core_privacy\local\request\userlist($sysctx, 'logstore_legacy');
provider::add_userids_for_context($userlist);
$systemuserids = $userlist->get_userids();
$this->assertCount(2, $systemuserids);
$this->assertNotFalse(array_search($u1->id, $systemuserids));
$this->assertNotFalse(array_search($u2->id, $systemuserids));
// Check the course context.
$userlist = new \core_privacy\local\request\userlist($c1ctx, 'logstore_legacy');
provider::add_userids_for_context($userlist);
$courseuserids = $userlist->get_userids();
$this->assertCount(2, $courseuserids);
$this->assertNotFalse(array_search($u1->id, $courseuserids));
$this->assertNotFalse(array_search($u3->id, $courseuserids));
// Check the module context.
$userlist = new \core_privacy\local\request\userlist($cm1ctx, 'logstore_legacy');
provider::add_userids_for_context($userlist);
$moduleuserids = $userlist->get_userids();
$this->assertCount(3, $moduleuserids);
$this->assertNotFalse(array_search($u1->id, $moduleuserids));
$this->assertNotFalse(array_search($u2->id, $moduleuserids));
$this->assertNotFalse(array_search($u3->id, $moduleuserids));
}
public function test_delete_data_for_user() {
global $DB;
@ -241,6 +306,68 @@ class logstore_legacy_privacy_testcase extends provider_testcase {
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u2->id]));
}
/**
* Test the deletion of data for a list of users in a context.
*/
public function test_delete_data_for_userlist() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$module = $this->getDataGenerator()->create_module('url', ['course' => $course]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($course->id);
$cm1ctx = context_module::instance($module->cmid);
$userctx = context_user::instance($u1->id);
$this->enable_logging();
$manager = get_log_manager(true);
$this->setUser($u1);
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 1]]);
$e->trigger();
$this->setUser($u2);
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 2]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]);
$e->trigger();
$this->setUser($u3);
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 4]]);
$e->trigger();
$this->setUser($u1);
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 5]]);
$e->trigger();
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 6]]);
$e->trigger();
$this->setUser($u2);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 7]]);
$e->trigger();
$this->setUser($u3);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 8]]);
$e->trigger();
// System context deleting one user.
$this->assertEquals(3, $DB->count_records('log', ['cmid' => 0, 'course' => 0]));
$userlist = new \core_privacy\local\request\approved_userlist($sysctx, 'logstore_legacy', [$u2->id]);
provider::delete_data_for_userlist($userlist);
$this->assertEquals(1, $DB->count_records('log', ['cmid' => 0, 'course' => 0]));
// Course context deleting one user.
$this->assertEquals(2, $DB->count_records('log', ['cmid' => 0, 'course' => $course->id]));
$userlist = new \core_privacy\local\request\approved_userlist($c1ctx, 'logstore_legacy', [$u1->id]);
provider::delete_data_for_userlist($userlist);
$this->assertEquals(1, $DB->count_records('log', ['cmid' => 0, 'course' => $course->id]));
// Module context deleting two users.
$this->assertEquals(3, $DB->count_records('log', ['cmid' => $module->cmid, 'course' => $course->id]));
$userlist = new \core_privacy\local\request\approved_userlist($cm1ctx, 'logstore_legacy', [$u1->id, $u3->id]);
provider::delete_data_for_userlist($userlist);
$this->assertEquals(1, $DB->count_records('log', ['cmid' => $module->cmid, 'course' => $course->id]));
}
public function test_export_data_for_user() {
global $DB;

View File

@ -40,7 +40,8 @@ use core_privacy\local\request\contextlist;
*/
class provider implements
\core_privacy\local\metadata\provider,
\tool_log\local\privacy\logstore_provider {
\tool_log\local\privacy\logstore_provider,
\tool_log\local\privacy\logstore_userlist_provider {
use \tool_log\local\privacy\moodle_database_export_and_delete;
@ -86,6 +87,22 @@ class provider implements
]);
}
/**
* Add user IDs that contain user information for the specified context.
*
* @param \core_privacy\local\request\userlist $userlist The userlist to add the users to.
* @return void
*/
public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) {
$params = ['contextid' => $userlist->get_context()->id];
$sql = "SELECT userid, relateduserid, realuserid
FROM {logstore_standard_log}
WHERE contextid = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
$userlist->add_from_sql('relateduserid', $sql, $params);
$userlist->add_from_sql('realuserid', $sql, $params);
}
/**
* Get the database object.
*

View File

@ -109,6 +109,52 @@ class logstore_standard_privacy_testcase extends provider_testcase {
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
}
public function test_add_userids_for_context() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$u4 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$this->enable_logging();
$manager = get_log_manager(true);
$userlist = new \core_privacy\local\request\userlist($sysctx, 'logstore_standard_log');
$userids = $userlist->get_userids();
$this->assertEmpty($userids);
provider::add_userids_for_context($userlist);
$userids = $userlist->get_userids();
$this->assertEmpty($userids);
// User one should be added (userid).
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
// User two (userids) and three (relateduserid) should be added.
$this->setUser($u2);
$e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx, 'relateduserid' => $u3->id]);
$e->trigger();
// The admin user should be added (realuserid).
$this->setAdminUser();
\core\session\manager::loginas($u2->id, context_system::instance());
$e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
// Set off an event in a different context. User 4 should not be returned below.
$this->setUser($u4);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
provider::add_userids_for_context($userlist);
$userids = $userlist->get_userids();
$this->assertCount(4, $userids);
$expectedresult = [$admin->id, $u1->id, $u2->id, $u3->id];
$this->assertEmpty(array_diff($expectedresult, $userids));
}
public function test_delete_data_for_user() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
@ -191,6 +237,48 @@ class logstore_standard_privacy_testcase extends provider_testcase {
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
public function test_delete_data_for_userlist() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$u4 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($course->id);
$this->enable_logging();
$manager = get_log_manager(true);
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
$this->setUser($u2);
$e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
$this->setUser($u3);
$e = \logstore_standard\event\unittest_executed::create(['context' => $sysctx]);
$e->trigger();
$this->setUser($u4);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
// Check that four records were created.
$this->assertEquals(4, $DB->count_records('logstore_standard_log'));
$userlist = new \core_privacy\local\request\approved_userlist($sysctx, 'logstore_standard_log', [$u1->id, $u3->id]);
provider::delete_data_for_userlist($userlist);
// We should have a record for u2 and u4.
$this->assertEquals(2, $DB->count_records('logstore_standard_log'));
$records = $DB->get_records('logstore_standard_log', ['contextid' => $sysctx->id]);
$this->assertCount(1, $records);
$currentrecord = array_shift($records);
$this->assertEquals($u2->id, $currentrecord->userid);
}
public function test_export_data_for_user() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();