Skip to content

Commit

Permalink
Add userlist privacy-provider
Browse files Browse the repository at this point in the history
  • Loading branch information
luukverhoeven committed May 15, 2020
1 parent 74c94d5 commit a03d951
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*dockermd_*/
node_modules/
node_modules/
privacy_test.php
81 changes: 80 additions & 1 deletion classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@

use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;

/**
Expand All @@ -42,7 +44,9 @@
* @copyright 31-10-2018 MFreak.nl
* @author Luuk Verhoeven
**/
class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider {
class provider implements \core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {

/**
* Returns meta data about this system.
Expand Down Expand Up @@ -319,4 +323,79 @@ public static function delete_data_for_user(approved_contextlist $contextlist) {
}
}

/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin
* combination.
*/
public static function get_users_in_context(userlist $userlist) : void {
$context = $userlist->get_context();

if (!is_a($context, \context_module::class)) {
return;
}

// Find users with gcanvas_attempt entries.
$sql = "
SELECT link.user_id
FROM {%s} link
JOIN {modules} m
ON m.name = :modulename
JOIN {course_modules} cm
ON cm.instance = link.gcanvas_id
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
AND ctx.contextlevel = :modlevel
WHERE ctx.id = :contextid";

$params = [
'modulename' => 'gcanvas',
'modlevel' => CONTEXT_MODULE,
'contextid' => $context->id,
];

$userlist->add_from_sql('user_id', sprintf($sql, 'gcanvas_attempt'), $params);
}

/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*
* @throws \coding_exception
* @throws \dml_exception
*/
public static function delete_data_for_users(approved_userlist $userlist) : void {
global $DB;

$context = $userlist->get_context();
$userids = $userlist->get_userids();

// Prepare SQL to gather all linked IDs.
[$insql, $inparams] = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$completedsql = "
SELECT link.id
FROM {%s} link
JOIN {modules} m
ON m.name = :modulename
JOIN {course_modules} cm
ON cm.instance = link.gcanvas_id
AND cm.module = m.id
WHERE cm.id = :instanceid
AND link.user_id $insql";

$completedparams = array_merge($inparams, [
'instanceid' => (int)$context->instanceid,
'modulename' => 'gcanvas',
]);

// Delete all gcanvas_attempt entries.
$attempts = $DB->get_fieldset_sql(sprintf($completedsql, 'gcanvas_attempt'), $completedparams);
if (!empty($attempts)) {
[$insql, $inparams] = $DB->get_in_or_equal($attempts, SQL_PARAMS_NAMED);
$DB->delete_records_select('gcanvas_attempt', "id $insql", $inparams);
}
}
}
209 changes: 209 additions & 0 deletions privacy-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<?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/>.

/**
* Testing tool classes/privacy/provider.php
* should be placed in the root of a plugin.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @copyright 2018Mfreak.nl
* @author Luuk Verhoeven
**/

use core_privacy\local\request\approved_userlist;

/**
* Only for testing purposes
* This is use for testing the privacy API
* fast way to test the privacy API
*/

define('CLI_SCRIPT', true);

require('../../config.php');

$plugin = new stdClass();
include 'version.php';

$action = $argv[1] ?? false;
$userid = $argv[2] ?? false;
if (empty($action)) {
die(PHP_EOL . 'Usage privacy-test.php [export|test|delete] userid (int)' . PHP_EOL);
}

if (empty($userid) || !is_number($userid)) {
die(PHP_EOL . ' argv 2 must be a Userid [not provided]' . PHP_EOL);
}

function check_implements($component, $interface) {
$manager = new \core_privacy\manager();
$rc = new \ReflectionClass(\core_privacy\manager::class);
$rcm = $rc->getMethod('component_implements');
$rcm->setAccessible(true);

return $rcm->invoke($manager, $component, $interface);
}

$provider = '\\' . $plugin->component . '\\privacy\\provider';

$user = \core_user::get_user($userid);
\core\session\manager::init_empty_session();
\core\session\manager::set_user($user);

// Get approvedlist $approvedlist
$manager = new \core_privacy\manager();
$approvedlist = new \core_privacy\local\request\contextlist_collection($user->id);
$contextlists = $manager->get_contexts_for_userid($user->id);
foreach ($contextlists as $contextlist) {
$approvedlist->add_contextlist(new \core_privacy\local\request\approved_contextlist(
$user,
$contextlist->get_component(),
$contextlist->get_contextids()
));
}

if ($action == 'test') {
echo "TEST " . PHP_EOL;

$rc = new \ReflectionClass(\core_privacy\manager::class);
$rcm = $rc->getMethod('get_component_list');
$rcm->setAccessible(true);

$manager = new \core_privacy\manager();
$components = $rcm->invoke($manager);

$list = (object)[
'good' => [],
'bad' => [],
];

foreach ($components as $component) {
if ($component !== $plugin->component) {
continue;
}
$compliant = $manager->component_is_compliant($component);
if ($compliant) {
$list->good[] = $component;
} else {
$list->bad[] = $component;
}
}

echo "The following plugins are not compliant:\n";
echo "=> " . implode("\n=> ", array_values($list->bad)) . "\n";

echo "\n";
echo "Testing the compliant plugins:\n";
foreach ($list->good as $component) {
$classname = \core_privacy\manager::get_provider_classname_for_component($component);
echo "== {$component} ($classname) ==\n";
if (check_implements($component, \core_privacy\local\metadata\null_provider::class)) {
echo " Claims not to store any data with reason:\n";
echo " '" . get_string($classname::get_reason(), $component) . "'\n";
} else if (check_implements($component, \core_privacy\local\metadata\provider::class)) {
$collection = new \core_privacy\local\metadata\collection($component);
$classname::get_metadata($collection);
$count = count($collection);
echo " Found {$count} items of metadata\n";
if (empty($count)) {
echo "!!! No metadata found!!! This an error.\n";
}

if (check_implements($component, \core_privacy\local\request\user_preference_provider::class)) {
$userprefdescribed = false;
foreach ($collection->get_collection() as $item) {
if ($item instanceof \core_privacy\local\metadata\types\user_preference) {
$userprefdescribed = true;
echo " " . $item->get_name() . " : " . get_string($item->get_summary(), $component) . "\n";
}
}
if (!$userprefdescribed) {
echo "!!! User preference found, but was not described in metadata\n";
}
}

if (check_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
// No need to check the return type - it's enforced by the interface.
$contextlist = $classname::get_contexts_for_userid($user->id);
$approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user, $contextlist->get_component(), $contextlist->get_contextids());
if (count($approvedcontextlist)) {
$classname::export_user_data($approvedcontextlist);
echo " Successfully ran a test export\n";
} else {
echo " Nothing to export.\n";
}
}
if (check_implements($component, \core_privacy\local\request\shared_data_provider::class)) {
echo " This is a shared data provider\n";
}
}
}

echo "\n\n== Done ==\n";
} else if ($action == 'export') {
echo "EXPORT " . PHP_EOL;

$exportedcontent = $manager->export_user_data($approvedlist);

echo "\n";
echo "== File was successfully exported to {$exportedcontent}\n";

$basedir = make_temp_directory('privacy');
$exportpath = make_unique_writable_directory($basedir, true);
$fp = get_file_packer();
$fp->extract_to_pathname($exportedcontent, $exportpath);

echo "== File export was uncompressed to {$exportpath}\n";
echo "============================================================================\n";
} else if ($action == 'delete') {
echo "DELETE " . PHP_EOL;

$contextlist = $provider::get_contexts_for_userid($user->id);
$contexts = $contextlist->get_contexts();

$context = reset($contexts);

$transaction = $DB->start_delegated_transaction();
$DB->get_record('user' , ['id' => $userid]);
$DB->set_debug(true);

if (empty($context)) {
die('Error - context empty');
}

echo "DELETE data for all users" . PHP_EOL;
$provider::delete_data_for_all_users_in_context($context);

echo "DELETE data for single users" . PHP_EOL;

/** @var \core_privacy\local\request\approved_contextlist $approved */
foreach ($approvedlist as $approved) {

if ($approved->get_component() == $plugin->component) {
// Test delete all users content by context.
// $provider::delete_data_for_user($approved);

$modulecontext = context_module::instance(319);

$approvedlist4 = new approved_userlist($modulecontext, $plugin->component, [(int)$userid]);
$provider::delete_data_for_users($approvedlist4);

$userlist = new \core_privacy\local\request\userlist($modulecontext, 'gcanvas');
$provider::get_users_in_context($userlist);
}
}
}

0 comments on commit a03d951

Please sign in to comment.