mirror of
https://github.com/moodle/moodle.git
synced 2025-04-22 17:02:03 +02:00
Merge branch 'MDL-62135-master' of git://github.com/FMCorz/moodle
This commit is contained in:
commit
028fb5c1f6
148
admin/tool/log/classes/local/privacy/helper.php
Normal file
148
admin/tool/log/classes/local/privacy/helper.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?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 helper.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\local\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_privacy\local\request\transform;
|
||||
|
||||
/**
|
||||
* Privacy helper class.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* Returns an event from a standard record.
|
||||
*
|
||||
* @see \logstore_standard\log\store::get_log_event()
|
||||
* @param object $data Log data.
|
||||
* @return \core\event\base
|
||||
*/
|
||||
protected static function restore_event_from_standard_record($data) {
|
||||
$extra = ['origin' => $data->origin, 'ip' => $data->ip, 'realuserid' => $data->realuserid];
|
||||
$data = (array) $data;
|
||||
$id = $data['id'];
|
||||
$data['other'] = unserialize($data['other']);
|
||||
if ($data['other'] === false) {
|
||||
$data['other'] = [];
|
||||
}
|
||||
unset($data['origin']);
|
||||
unset($data['ip']);
|
||||
unset($data['realuserid']);
|
||||
unset($data['id']);
|
||||
|
||||
if (!$event = \core\event\base::restore($data, $extra)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a standard log record for a user.
|
||||
*
|
||||
* @param object $record The record.
|
||||
* @param int $userid The user ID.
|
||||
* @return array
|
||||
*/
|
||||
public static function transform_standard_log_record_for_userid($record, $userid) {
|
||||
|
||||
// Restore the event to try to get the name, description and other field.
|
||||
$restoredevent = static::restore_event_from_standard_record($record);
|
||||
if ($restoredevent) {
|
||||
$name = $restoredevent->get_name();
|
||||
$description = $restoredevent->get_description();
|
||||
$other = $restoredevent->other;
|
||||
|
||||
} else {
|
||||
$name = $record->eventname;
|
||||
$description = "Unknown event ({$name})";
|
||||
$other = unserialize($record->other);
|
||||
}
|
||||
|
||||
$realuserid = $record->realuserid;
|
||||
$isauthor = $record->userid == $userid;
|
||||
$isrelated = $record->relateduserid == $userid;
|
||||
$isrealuser = $realuserid == $userid;
|
||||
$ismasqueraded = $realuserid !== null && $record->userid != $realuserid;
|
||||
$ismasquerading = $isrealuser && !$isauthor;
|
||||
$isanonymous = $record->anonymous;
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'timecreated' => transform::datetime($record->timecreated),
|
||||
'ip' => $record->ip,
|
||||
'origin' => static::transform_origin($record->origin),
|
||||
'other' => $other ? $other : []
|
||||
];
|
||||
|
||||
if ($isanonymous) {
|
||||
$data['action_was_done_anonymously'] = transform::yesno($isanonymous);
|
||||
}
|
||||
if ($isauthor || !$isanonymous) {
|
||||
$data['authorid'] = transform::user($record->userid);
|
||||
$data['author_of_the_action_was_you'] = transform::yesno($isauthor);
|
||||
}
|
||||
|
||||
if ($record->relateduserid) {
|
||||
$data['relateduserid'] = transform::user($record->relateduserid);
|
||||
$data['related_user_was_you'] = transform::yesno($isrelated);
|
||||
}
|
||||
|
||||
if ($ismasqueraded) {
|
||||
$data['author_of_the_action_was_masqueraded'] = transform::yesno(true);
|
||||
if ($ismasquerading || !$isanonymous) {
|
||||
$data['masqueradinguserid'] = transform::user($realuserid);
|
||||
$data['masquerading_user_was_you'] = transform::yesno($ismasquerading);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform origin.
|
||||
*
|
||||
* @param string $origin The page request origin.
|
||||
* @return string
|
||||
*/
|
||||
public static function transform_origin($origin) {
|
||||
switch ($origin) {
|
||||
case 'cli':
|
||||
case 'restore':
|
||||
case 'web':
|
||||
case 'ws':
|
||||
return get_string('privacy:request:origin:' . $origin, 'tool_log');
|
||||
break;
|
||||
}
|
||||
return $origin;
|
||||
}
|
||||
}
|
78
admin/tool/log/classes/local/privacy/logstore_provider.php
Normal file
78
admin/tool/log/classes/local/privacy/logstore_provider.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?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 provider interface.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\local\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
|
||||
/**
|
||||
* Logstore provider interface.
|
||||
*
|
||||
* Logstore subplugins providers must implement this interface.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface logstore_provider extends \core_privacy\local\request\plugin\subplugin_provider {
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid);
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
* @return void
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist);
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context);
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist);
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Moodle database: export and delete.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\local\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\writer;
|
||||
|
||||
/**
|
||||
* Moodle database: export and delete trait.
|
||||
*
|
||||
* This is to be used with logstores which use a database and table with the same columns
|
||||
* as the core plugin 'logstore_standard'.
|
||||
*
|
||||
* This trait expects the following methods to be present in the object:
|
||||
*
|
||||
* - public static function get_database_and_table(): [moodle_database|null, string|null]
|
||||
* - public static function get_export_subcontext(): []
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait moodle_database_export_and_delete {
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userid = $contextlist->get_user()->id;
|
||||
list($insql, $inparams) = $db->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
|
||||
$sql = "(userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3) AND contextid $insql";
|
||||
$params = array_merge($inparams, [
|
||||
'userid1' => $userid,
|
||||
'userid2' => $userid,
|
||||
'userid3' => $userid,
|
||||
]);
|
||||
|
||||
$path = static::get_export_subcontext();
|
||||
$flush = function($lastcontextid, $data) use ($path) {
|
||||
$context = context::instance_by_id($lastcontextid);
|
||||
writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
|
||||
};
|
||||
|
||||
$lastcontextid = null;
|
||||
$data = [];
|
||||
$recordset = $db->get_recordset_select($table, $sql, $params, 'contextid, timecreated, id');
|
||||
foreach ($recordset as $record) {
|
||||
if ($lastcontextid && $lastcontextid != $record->contextid) {
|
||||
$flush($lastcontextid, $data);
|
||||
$data = [];
|
||||
}
|
||||
$data[] = helper::transform_standard_log_record_for_userid($record, $userid);
|
||||
$lastcontextid = $record->contextid;
|
||||
}
|
||||
if ($lastcontextid) {
|
||||
$flush($lastcontextid, $data);
|
||||
}
|
||||
$recordset->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
$db->delete_records($table, ['contextid' => $context->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
list($insql, $inparams) = $db->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
$params = array_merge($inparams, ['userid' => $contextlist->get_user()->id]);
|
||||
$db->delete_records_select($table, "userid = :userid AND contextid $insql", $params);
|
||||
}
|
||||
|
||||
}
|
113
admin/tool/log/classes/privacy/provider.php
Normal file
113
admin/tool/log/classes/privacy/provider.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use tool_log\log\manager;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @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\subsystem\provider {
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_plugintype_link('logstore', [], 'privacy:metadata:logstore');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing 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();
|
||||
static::call_subplugins_method_with_args('add_contexts_for_userid', [$contextlist, $userid]);
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
static::call_subplugins_method_with_args('export_user_data', [$contextlist]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
static::call_subplugins_method_with_args('delete_data_for_all_users_in_context', [$context]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
static::call_subplugins_method_with_args('delete_data_for_user', [$contextlist]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the subplugins method with arguments.
|
||||
*
|
||||
* @param string $method The method name.
|
||||
* @param array $args The arguments.
|
||||
* @return void
|
||||
*/
|
||||
protected static function call_subplugins_method_with_args($method, array $args = []) {
|
||||
$interface = 'tool_log\local\privacy\logstore_provider';
|
||||
$subplugins = manager::get_store_plugins();
|
||||
foreach ($subplugins as $subplugin => $unused) {
|
||||
\core_privacy\manager::component_class_callback($subplugin, $interface, $method, $args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -26,7 +26,13 @@ $string['actlogshdr'] = 'Available log stores';
|
||||
$string['configlogplugins'] = 'Please enable all required plugins and arrange them in appropriate order.';
|
||||
$string['logging'] = 'Logging';
|
||||
$string['managelogging'] = 'Manage log stores';
|
||||
$string['reportssupported'] = 'Reports supported';
|
||||
$string['pluginname'] = 'Log store manager';
|
||||
$string['privacy:metadata:logstore'] = 'The log stores';
|
||||
$string['privacy:path:logs'] = 'Logs';
|
||||
$string['privacy:request:origin:cli'] = 'Command line tool';
|
||||
$string['privacy:request:origin:restore'] = 'Backup being restored';
|
||||
$string['privacy:request:origin:web'] = 'Standard web request';
|
||||
$string['privacy:request:origin:ws'] = 'Mobile app or web service';
|
||||
$string['reportssupported'] = 'Reports supported';
|
||||
$string['subplugintype_logstore'] = 'Log store';
|
||||
$string['subplugintype_logstore_plural'] = 'Log stores';
|
||||
|
114
admin/tool/log/store/database/classes/privacy/provider.php
Normal file
114
admin/tool/log/store/database/classes/privacy/provider.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace logstore_database\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\contextlist;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\tool_log\local\privacy\logstore_provider {
|
||||
|
||||
use \tool_log\local\privacy\moodle_database_export_and_delete;
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_external_location_link('log', [
|
||||
'eventname' => 'privacy:metadata:log:eventname',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'relateduserid' => 'privacy:metadata:log:relateduserid',
|
||||
'anonymous' => 'privacy:metadata:log:anonymous',
|
||||
'other' => 'privacy:metadata:log:other',
|
||||
'timecreated' => 'privacy:metadata:log:timecreated',
|
||||
'origin' => 'privacy:metadata:log:origin',
|
||||
'ip' => 'privacy:metadata:log:ip',
|
||||
'realuserid' => 'privacy:metadata:log:realuserid',
|
||||
], 'privacy:metadata:log');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = 'userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3';
|
||||
$params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
|
||||
$contextids = $db->get_fieldset_select($table, 'DISTINCT contextid', $sql, $params);
|
||||
if (empty($contextids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = implode(' UNION ', array_map(function($id) use ($db) {
|
||||
return 'SELECT ' . $id . $db->sql_null_from_clause();
|
||||
}, $contextids));
|
||||
$contextlist->add_from_sql($sql, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database object.
|
||||
*
|
||||
* @return array Containing moodle_database, string, or null values.
|
||||
*/
|
||||
protected static function get_database_and_table() {
|
||||
$manager = get_log_manager();
|
||||
$store = new \logstore_database\log\store($manager);
|
||||
$db = $store->get_extdb();
|
||||
return $db ? [$db, $store->get_config_value('dbtable')] : [null, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to export the logs to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_export_subcontext() {
|
||||
return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')];
|
||||
}
|
||||
}
|
@ -44,6 +44,16 @@ $string['other'] = 'Other';
|
||||
$string['participating'] = 'Participating';
|
||||
$string['pluginname'] = 'External database log';
|
||||
$string['pluginname_desc'] = 'A log plugin that stores log entries in an external database table.';
|
||||
$string['privacy:metadata:log'] = 'A collection of past events';
|
||||
$string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous';
|
||||
$string['privacy:metadata:log:eventname'] = 'The event name';
|
||||
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
|
||||
$string['privacy:metadata:log:origin'] = 'The origin of the event';
|
||||
$string['privacy:metadata:log:other'] = 'Additional information about the event';
|
||||
$string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.';
|
||||
$string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event';
|
||||
$string['privacy:metadata:log:timecreated'] = 'The time at which the event occurred';
|
||||
$string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event';
|
||||
$string['read'] = 'Read';
|
||||
$string['tablenotfound'] = 'Specified table was not found';
|
||||
$string['teaching'] = 'Teaching';
|
||||
|
416
admin/tool/log/store/database/tests/privacy_test.php
Normal file
416
admin/tool/log/store/database/tests/privacy_test.php
Normal file
@ -0,0 +1,416 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use logstore_database\privacy\provider;
|
||||
|
||||
require_once(__DIR__ . '/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* This testcase is almost identical to the logstore_standard testcase, aside from the
|
||||
* initialisation of the relevant logstore obviously.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class logstore_database_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
$this->resetAfterTest();
|
||||
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
|
||||
|
||||
// Fake the settings, we will abuse the standard plugin table here...
|
||||
set_config('dbdriver', $CFG->dblibrary . '/' . $CFG->dbtype, 'logstore_database');
|
||||
set_config('dbhost', $CFG->dbhost, 'logstore_database');
|
||||
set_config('dbuser', $CFG->dbuser, 'logstore_database');
|
||||
set_config('dbpass', $CFG->dbpass, 'logstore_database');
|
||||
set_config('dbname', $CFG->dbname, 'logstore_database');
|
||||
set_config('dbtable', $CFG->prefix . 'logstore_standard_log', 'logstore_database');
|
||||
if (!empty($CFG->dboptions['dbpersist'])) {
|
||||
set_config('dbpersist', 1, 'logstore_database');
|
||||
} else {
|
||||
set_config('dbpersist', 0, 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbsocket'])) {
|
||||
set_config('dbsocket', $CFG->dboptions['dbsocket'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbsocket', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbport'])) {
|
||||
set_config('dbport', $CFG->dboptions['dbport'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbport', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbschema'])) {
|
||||
set_config('dbschema', $CFG->dboptions['dbschema'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbschema', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbcollation'])) {
|
||||
set_config('dbcollation', $CFG->dboptions['dbcollation'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbcollation', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbhandlesoptions'])) {
|
||||
set_config('dbhandlesoptions', $CFG->dboptions['dbhandlesoptions'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbhandlesoptions', false, 'logstore_database');
|
||||
}
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $cm1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
|
||||
// User 2 is the related user.
|
||||
$this->setUser(0);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]);
|
||||
|
||||
// Admin user is the real user.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
|
||||
// By admin user masquerading u1 related to u3.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u1->id, context_system::instance());
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_database', [$c1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
$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();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')];
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is related.
|
||||
$this->setUser(0);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id,
|
||||
'other' => ['i' => 1]]);
|
||||
$e->trigger();
|
||||
|
||||
// Admin user masquerades u3, which is related to u4.
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id,
|
||||
'other' => ['i' => 2]]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertSame(0, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertSame(1, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Add anonymous events.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the content of a context list.
|
||||
*
|
||||
* @param contextlist $contextlist The collection.
|
||||
* @param array $expected List of expected contexts or IDs.
|
||||
* @return void
|
||||
*/
|
||||
protected function assert_contextlist_equals($contextlist, array $expected) {
|
||||
$expectedids = array_map(function($context) {
|
||||
if (is_object($context)) {
|
||||
return $context->id;
|
||||
}
|
||||
return $context;
|
||||
}, $expected);
|
||||
$contextids = array_map('intval', $contextlist->get_contextids());
|
||||
sort($contextids);
|
||||
sort($expectedids);
|
||||
$this->assertEquals($expectedids, $contextids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_database', 'tool_log');
|
||||
set_config('buffersize', 0, 'logstore_database');
|
||||
set_config('logguests', 1, 'logstore_database');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contextlist for a user.
|
||||
*
|
||||
* @param object $user The user.
|
||||
* @return contextlist
|
||||
*/
|
||||
protected function get_contextlist_for_user($user) {
|
||||
$contextlist = new contextlist();
|
||||
provider::add_contexts_for_userid($contextlist, $user->id);
|
||||
return $contextlist;
|
||||
}
|
||||
}
|
220
admin/tool/log/store/legacy/classes/privacy/provider.php
Normal file
220
admin/tool/log/store/legacy/classes/privacy/provider.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace logstore_legacy\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
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 tool_log\local\privacy\helper;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\tool_log\local\privacy\logstore_provider {
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_external_location_link('log', [
|
||||
'time' => 'privacy:metadata:log:time',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'ip' => 'privacy:metadata:log:ip',
|
||||
'action' => 'privacy:metadata:log:action',
|
||||
'url' => 'privacy:metadata:log:url',
|
||||
'info' => 'privacy:metadata:log:info',
|
||||
], 'privacy:metadata:log');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
|
||||
$sql = "
|
||||
SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {log} l
|
||||
ON (l.cmid = 0 AND l.course = ctx.instanceid AND ctx.contextlevel = :courselevel)
|
||||
OR (l.cmid > 0 AND l.cmid = ctx.instanceid AND ctx.contextlevel = :modulelevel)
|
||||
OR (l.course <= 0 AND ctx.id = :syscontextid)
|
||||
WHERE l.userid = :userid";
|
||||
$params = [
|
||||
'courselevel' => CONTEXT_COURSE,
|
||||
'modulelevel' => CONTEXT_MODULE,
|
||||
'syscontextid' => SYSCONTEXTID,
|
||||
'userid' => $userid,
|
||||
];
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
$userid = $contextlist->get_user()->id;
|
||||
list($insql, $inparams) = static::get_sql_where_from_contexts($contextlist->get_contexts());
|
||||
if (empty($insql)) {
|
||||
return;
|
||||
}
|
||||
$sql = "userid = :userid AND $insql";
|
||||
$params = array_merge($inparams, ['userid' => $userid]);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')];
|
||||
$flush = function($lastcontextid, $data) use ($path) {
|
||||
$context = context::instance_by_id($lastcontextid);
|
||||
writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
|
||||
};
|
||||
|
||||
$lastcontextid = null;
|
||||
$data = [];
|
||||
$recordset = $DB->get_recordset_select('log', $sql, $params, 'course, cmid, time, id');
|
||||
foreach ($recordset as $record) {
|
||||
$event = \logstore_legacy\event\legacy_logged::restore_legacy($record);
|
||||
$context = $event->get_context();
|
||||
if ($lastcontextid && $lastcontextid != $context->id) {
|
||||
$flush($lastcontextid, $data);
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$extra = $event->get_logextra();
|
||||
$data[] = [
|
||||
'name' => $event->get_name(),
|
||||
'description' => $event->get_description(),
|
||||
'timecreated' => transform::datetime($event->timecreated),
|
||||
'ip' => $extra['ip'],
|
||||
'origin' => helper::transform_origin($extra['origin']),
|
||||
];
|
||||
|
||||
$lastcontextid = $context->id;
|
||||
}
|
||||
if ($lastcontextid) {
|
||||
$flush($lastcontextid, $data);
|
||||
}
|
||||
$recordset->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
global $DB;
|
||||
list($sql, $params) = static::get_sql_where_from_contexts([$context]);
|
||||
if (empty($sql)) {
|
||||
return;
|
||||
}
|
||||
$DB->delete_records_select('log', $sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
list($sql, $params) = static::get_sql_where_from_contexts($contextlist->get_contexts());
|
||||
if (empty($sql)) {
|
||||
return;
|
||||
}
|
||||
$userid = $contextlist->get_user()->id;
|
||||
$DB->delete_records_select('log', "$sql AND userid = :userid", array_merge($params, ['userid' => $userid]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an SQL where statement from a list of contexts.
|
||||
*
|
||||
* @param array $contexts The contexts.
|
||||
* @return array [$sql, $params]
|
||||
*/
|
||||
protected static function get_sql_where_from_contexts(array $contexts) {
|
||||
global $DB;
|
||||
|
||||
$sorted = array_reduce($contexts, function ($carry, $context) {
|
||||
$level = $context->contextlevel;
|
||||
if ($level == CONTEXT_MODULE || $level == CONTEXT_COURSE) {
|
||||
$carry[$level][] = $context->instanceid;
|
||||
} else if ($level == CONTEXT_SYSTEM) {
|
||||
$carry[$level] = $context->id;
|
||||
}
|
||||
return $carry;
|
||||
}, [
|
||||
CONTEXT_COURSE => [],
|
||||
CONTEXT_MODULE => [],
|
||||
CONTEXT_SYSTEM => null,
|
||||
]);
|
||||
|
||||
$sqls = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($sorted[CONTEXT_MODULE])) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_MODULE], SQL_PARAMS_NAMED);
|
||||
$sqls[] = "cmid $insql";
|
||||
$params = array_merge($params, $inparams);
|
||||
}
|
||||
|
||||
if (!empty($sorted[CONTEXT_COURSE])) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_COURSE], SQL_PARAMS_NAMED);
|
||||
|
||||
$sqls[] = "cmid = 0 AND course $insql";
|
||||
$params = array_merge($params, $inparams);
|
||||
}
|
||||
|
||||
if (!empty($sorted[CONTEXT_SYSTEM])) {
|
||||
$sqls[] = "course <= 0";
|
||||
}
|
||||
|
||||
if (empty($sqls)) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
return ['((' . implode(') OR (', $sqls) . '))', $params];
|
||||
}
|
||||
}
|
@ -27,4 +27,11 @@ $string['loglegacy'] = 'Log legacy data';
|
||||
$string['loglegacy_help'] = 'This plugin records log data to the legacy log table (mdl_log). This functionality has been replaced by newer, richer and more efficient logging plugins, so you should only run this plugin if you have old custom reports that directly query the old log table. Writing to the legacy logs will increase load, so it is recommended that you disable this plugin for performance reasons when it is not needed.';
|
||||
$string['pluginname'] = 'Legacy log';
|
||||
$string['pluginname_desc'] = 'A log plugin that stores log entries in the legacy log table.';
|
||||
$string['privacy:metadata:log'] = 'A collection of past events';
|
||||
$string['privacy:metadata:log:action'] = 'A description of the action';
|
||||
$string['privacy:metadata:log:info'] = 'Additional information';
|
||||
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
|
||||
$string['privacy:metadata:log:time'] = 'The date at wich the action took place';
|
||||
$string['privacy:metadata:log:url'] = 'The URL related to the event';
|
||||
$string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action';
|
||||
$string['taskcleanup'] = 'Legacy log table cleanup';
|
||||
|
344
admin/tool/log/store/legacy/tests/privacy_test.php
Normal file
344
admin/tool/log/store/legacy/tests/privacy_test.php
Normal file
@ -0,0 +1,344 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use logstore_legacy\privacy\provider;
|
||||
use logstore_legacy\event\unittest_executed;
|
||||
|
||||
require_once(__DIR__ . '/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class logstore_legacy_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm1ctx]);
|
||||
|
||||
// User 3 is the author.
|
||||
$this->setUser($u3);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
|
||||
$e->trigger();
|
||||
|
||||
// Assert what we have.
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete other context.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c2ctx->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete system.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
|
||||
$e->trigger();
|
||||
|
||||
// Assert what we have.
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete other context.
|
||||
provider::delete_data_for_all_users_in_context($c2ctx);
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete system.
|
||||
provider::delete_data_for_all_users_in_context($sysctx);
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_all_users_in_context($cm1ctx);
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')];
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
|
||||
$e->trigger();
|
||||
|
||||
// Test export.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id]));
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($cm1ctx)->get_data($path);
|
||||
$this->assertCount(2, $data->logs);
|
||||
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id]));
|
||||
$data = writer::with_context($cm1ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id]));
|
||||
$data = writer::with_context($sysctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the content of a context list.
|
||||
*
|
||||
* @param contextlist $contextlist The collection.
|
||||
* @param array $expected List of expected contexts or IDs.
|
||||
* @return void
|
||||
*/
|
||||
protected function assert_contextlist_equals($contextlist, array $expected) {
|
||||
$expectedids = array_map(function($context) {
|
||||
if (is_object($context)) {
|
||||
return $context->id;
|
||||
}
|
||||
return $context;
|
||||
}, $expected);
|
||||
$contextids = array_map('intval', $contextlist->get_contextids());
|
||||
sort($contextids);
|
||||
sort($expectedids);
|
||||
$this->assertEquals($expectedids, $contextids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_legacy', 'tool_log');
|
||||
set_config('loglegacy', 1, 'logstore_legacy');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contextlist for a user.
|
||||
*
|
||||
* @param object $user The user.
|
||||
* @return contextlist
|
||||
*/
|
||||
protected function get_contextlist_for_user($user) {
|
||||
$contextlist = new contextlist();
|
||||
provider::add_contexts_for_userid($contextlist, $user->id);
|
||||
return $contextlist;
|
||||
}
|
||||
}
|
109
admin/tool/log/store/standard/classes/privacy/provider.php
Normal file
109
admin/tool/log/store/standard/classes/privacy/provider.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace logstore_standard\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\contextlist;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\tool_log\local\privacy\logstore_provider {
|
||||
|
||||
use \tool_log\local\privacy\moodle_database_export_and_delete;
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_database_table('log', [
|
||||
'eventname' => 'privacy:metadata:log:eventname',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'relateduserid' => 'privacy:metadata:log:relateduserid',
|
||||
'anonymous' => 'privacy:metadata:log:anonymous',
|
||||
'other' => 'privacy:metadata:log:other',
|
||||
'timecreated' => 'privacy:metadata:log:timecreated',
|
||||
'origin' => 'privacy:metadata:log:origin',
|
||||
'ip' => 'privacy:metadata:log:ip',
|
||||
'realuserid' => 'privacy:metadata:log:realuserid',
|
||||
], 'privacy:metadata:log');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
|
||||
$sql = "
|
||||
SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {logstore_standard_log} l
|
||||
ON l.contextid = ctx.id
|
||||
WHERE l.userid = :userid1
|
||||
OR l.relateduserid = :userid2
|
||||
OR l.realuserid = :userid3";
|
||||
$contextlist->add_from_sql($sql, [
|
||||
'userid1' => $userid,
|
||||
'userid2' => $userid,
|
||||
'userid3' => $userid,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database object.
|
||||
*
|
||||
* @return array Containing moodle_database, string, or null values.
|
||||
*/
|
||||
protected static function get_database_and_table() {
|
||||
global $DB;
|
||||
return [$DB, 'logstore_standard_log'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to export the logs to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_export_subcontext() {
|
||||
return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
|
||||
}
|
||||
}
|
@ -25,4 +25,14 @@
|
||||
$string['buffersize'] = 'Write buffer size';
|
||||
$string['pluginname'] = 'Standard log';
|
||||
$string['pluginname_desc'] = 'A log plugin stores log entries in a Moodle database table.';
|
||||
$string['privacy:metadata:log'] = 'A collection of past events';
|
||||
$string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous';
|
||||
$string['privacy:metadata:log:eventname'] = 'The event name';
|
||||
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
|
||||
$string['privacy:metadata:log:origin'] = 'The origin of the event';
|
||||
$string['privacy:metadata:log:other'] = 'Additional information about the event';
|
||||
$string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.';
|
||||
$string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event';
|
||||
$string['privacy:metadata:log:timecreated'] = 'The time at which the event occurred';
|
||||
$string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event';
|
||||
$string['taskcleanup'] = 'Log table cleanup';
|
||||
|
373
admin/tool/log/store/standard/tests/privacy_test.php
Normal file
373
admin/tool/log/store/standard/tests/privacy_test.php
Normal file
@ -0,0 +1,373 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use logstore_standard\privacy\provider;
|
||||
|
||||
require_once(__DIR__ . '/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class logstore_standard_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $cm1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
|
||||
// User 2 is the related user.
|
||||
$this->setUser(0);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]);
|
||||
|
||||
// Admin user is the real user.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
|
||||
// By admin user masquerading u1 related to u3.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u1->id, context_system::instance());
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_standard', [$c1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
$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();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is related.
|
||||
$this->setUser(0);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id,
|
||||
'other' => ['i' => 1]]);
|
||||
$e->trigger();
|
||||
|
||||
// Admin user masquerades u3, which is related to u4.
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id,
|
||||
'other' => ['i' => 2]]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertSame(0, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertSame(1, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Add anonymous events.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the content of a context list.
|
||||
*
|
||||
* @param contextlist $contextlist The collection.
|
||||
* @param array $expected List of expected contexts or IDs.
|
||||
* @return void
|
||||
*/
|
||||
protected function assert_contextlist_equals($contextlist, array $expected) {
|
||||
$expectedids = array_map(function($context) {
|
||||
if (is_object($context)) {
|
||||
return $context->id;
|
||||
}
|
||||
return $context;
|
||||
}, $expected);
|
||||
$contextids = array_map('intval', $contextlist->get_contextids());
|
||||
sort($contextids);
|
||||
sort($expectedids);
|
||||
$this->assertEquals($expectedids, $contextids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
set_config('buffersize', 0, 'logstore_standard');
|
||||
set_config('logguests', 1, 'logstore_standard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contextlist for a user.
|
||||
*
|
||||
* @param object $user The user.
|
||||
* @return contextlist
|
||||
*/
|
||||
protected function get_contextlist_for_user($user) {
|
||||
$contextlist = new contextlist();
|
||||
provider::add_contexts_for_userid($contextlist, $user->id);
|
||||
return $contextlist;
|
||||
}
|
||||
}
|
176
admin/tool/log/tests/privacy_test.php
Normal file
176
admin/tool/log/tests/privacy_test.php
Normal file
@ -0,0 +1,176 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package tool_log
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use tool_log\privacy\provider;
|
||||
|
||||
require_once($CFG->dirroot . '/admin/tool/log/store/standard/tests/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* We're not testing the full functionality, just that the provider passes the requests
|
||||
* down to at least one of its subplugin. Each subplugin should have tests to cover the
|
||||
* different provider methods in depth.
|
||||
*
|
||||
* @package tool_log
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_log_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
$this->setUser($u1);
|
||||
$this->assertEmpty(provider::get_contexts_for_userid($u1->id)->get_contextids(), []);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$this->assertEquals($c1ctx->id, provider::get_contexts_for_userid($u1->id)->get_contextids()[0]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_standard', [$c1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 123]]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'tool_log', [$c1ctx->id]));
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertSame(123, $data->logs[0]['other']['i']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
set_config('buffersize', 0, 'logstore_standard');
|
||||
set_config('logguests', 1, 'logstore_standard');
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user