Merge branch 'MDL-62564-integration-master-1' of git://github.com/mihailges/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2018-11-08 16:38:38 +01:00
commit 3aa0cfe33e
16 changed files with 644 additions and 29 deletions

View file

@ -41,6 +41,7 @@ use tool_dataprivacy\external\data_request_exporter;
use tool_dataprivacy\local\helper;
use tool_dataprivacy\task\initiate_data_request_task;
use tool_dataprivacy\task\process_data_request_task;
use tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
@ -253,6 +254,8 @@ class api {
$datarequest->set('type', $type);
// Set request comments.
$datarequest->set('comments', $comments);
// Set the creation method.
$datarequest->set('creationmethod', $creationmethod);
// Store subject access request.
$datarequest->create();
@ -275,6 +278,7 @@ class api {
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @param int[] $creationmethods The request creation method filters.
* @param string $sort The order by clause.
* @param int $offset Amount of records to skip.
* @param int $limit Amount of records to fetch.
@ -282,7 +286,8 @@ class api {
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $sort = '', $offset = 0, $limit = 0) {
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $creationmethods = [],
$sort = '', $offset = 0, $limit = 0) {
global $DB, $USER;
$results = [];
$sqlparams = [];
@ -306,6 +311,13 @@ class api {
$sqlparams = array_merge($sqlparams, $typeparams);
}
// Set request creation method filter.
if (!empty($creationmethods)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($creationmethods, SQL_PARAMS_NAMED);
$sqlconditions[] = "creationmethod $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
@ -348,7 +360,7 @@ class api {
if (!empty($expiredrequests)) {
data_request::expire($expiredrequests);
$results = self::get_data_requests($userid, $statuses, $types, $sort, $offset, $limit);
$results = self::get_data_requests($userid, $statuses, $types, $creationmethods, $sort, $offset, $limit);
}
}
@ -361,11 +373,12 @@ class api {
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @param int[] $creationmethods The request creation method filters.
* @return int
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests_count($userid = 0, $statuses = [], $types = []) {
public static function get_data_requests_count($userid = 0, $statuses = [], $types = [], $creationmethods = []) {
global $DB, $USER;
$count = 0;
$sqlparams = [];
@ -379,6 +392,11 @@ class api {
$sqlconditions[] = "type $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if (!empty($creationmethods)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($creationmethods, SQL_PARAMS_NAMED);
$sqlconditions[] = "creationmethod $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
@ -965,6 +983,34 @@ class api {
return data_registry::get_effective_contextlevel_value($contextlevel, 'purpose', $forcedvalue);
}
/**
* Creates an expired context record for the provided context id.
*
* @param int $contextid
* @return \tool_dataprivacy\expired_context
*/
public static function create_expired_context($contextid) {
$record = (object)[
'contextid' => $contextid,
'status' => expired_context::STATUS_EXPIRED,
];
$expiredctx = new expired_context(0, $record);
$expiredctx->save();
return $expiredctx;
}
/**
* Deletes an expired context record.
*
* @param int $id The tool_dataprivacy_ctxexpire id.
* @return bool True on success.
*/
public static function delete_expired_context($id) {
$expiredcontext = new expired_context($id);
return $expiredcontext->delete();
}
/**
* Updates the status of an expired context.
*

View file

@ -21,7 +21,9 @@
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
use core\persistent;

View file

@ -0,0 +1,63 @@
<?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/>.
/**
* Event observers supported by this module.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\event;
use \tool_dataprivacy\api;
use \tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
/**
* Event observers supported by this module.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_deleted_observer {
/**
* Create user data deletion request when the user is deleted.
*
* @param \core\event\user_deleted $event
*/
public static function create_delete_data_request(\core\event\user_deleted $event) {
// Automatic creation of deletion requests must be enabled.
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
$requesttypes = [api::DATAREQUEST_TYPE_DELETE];
$requeststatuses = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DELETED];
$hasongoingdeleterequests = api::has_ongoing_request($event->objectid, $requesttypes[0]);
$hascompleteddeleterequest = (api::get_data_requests_count($event->objectid, $requeststatuses,
$requesttypes) > 0) ? true : false;
if (!$hasongoingdeleterequests && !$hascompleteddeleterequest) {
api::create_data_request($event->objectid, $requesttypes[0],
get_string('datarequestcreatedupondelete', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO);
}
}
}
}

View file

@ -0,0 +1,149 @@
<?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/>.
/**
* Expired contexts manager for CONTEXT_USER.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use core_privacy\manager;
defined('MOODLE_INTERNAL') || die();
/**
* Expired contexts manager for CONTEXT_USER.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
/**
* Only user level.
*
* @return int[]
*/
protected function get_context_levels() {
return [CONTEXT_USER];
}
/**
* Returns the user context instances that are expired.
*
* @return \stdClass[]
*/
protected function get_expired_contexts() {
global $DB;
// Including context info + last login timestamp.
$fields = 'ctx.id AS id, ' . \context_helper::get_preload_record_columns_sql('ctx');
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
// Calculate what is considered expired according to the context level effective purpose (= now + retention period).
$expiredtime = new \DateTime();
$retention = new \DateInterval($purpose->get('retentionperiod'));
$expiredtime->sub($retention);
$sql = "SELECT $fields FROM {context} ctx
JOIN {user} u ON ctx.contextlevel = ? AND ctx.instanceid = u.id
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx ON ctx.id = expiredctx.contextid
WHERE u.lastaccess <= ? AND u.lastaccess > 0 AND expiredctx.id IS NULL
ORDER BY ctx.path, ctx.contextlevel ASC";
$possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_USER, $expiredtime->getTimestamp()]);
$expiredcontexts = [];
foreach ($possiblyexpired as $record) {
\context_helper::preload_from_record($record);
// No strict checking as the context may already be deleted (e.g. we just deleted a course,
// module contexts below it will not exist).
$context = \context::instance_by_id($record->id, false);
if (!$context) {
continue;
}
if (is_siteadmin($context->instanceid)) {
continue;
}
$courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
foreach ($courses as $course) {
if (!$course->enddate) {
// We can not know it what is going on here, so we prefer to be conservative.
continue 2;
}
if ($course->enddate >= time()) {
// Future or ongoing course.
continue 2;
}
}
$expiredcontexts[$context->id] = $context;
}
return $expiredcontexts;
}
/**
* Deletes user data from the provided context.
*
* Overwritten to delete the user.
*
* @param manager $privacymanager
* @param expired_context $expiredctx
* @return \context|false
*/
protected function delete_expired_context(manager $privacymanager, expired_context $expiredctx) {
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
if (!$context) {
return false;
}
if (!PHPUNIT_TEST) {
mtrace('Deleting context ' . $context->id . ' - ' .
shorten_text($context->get_context_name(true, true)));
}
// To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
// contexts for the user that may still exist and call delete_data_for_user().
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
$approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
$contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
foreach ($contextlistcollection as $contextlist) {
$approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
$user,
$contextlist->get_component(),
$contextlist->get_contextids()
));
}
$privacymanager->delete_data_for_user($approvedlistcollection);
api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
// Delete the user.
delete_user($user);
return $context;
}
}

View file

@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
use coding_exception;
use moodle_exception;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
/**
* Class containing helper functions for the data privacy tool.
@ -44,6 +45,9 @@ class helper {
/** Filter constant associated with the request status filter. */
const FILTER_STATUS = 2;
/** Filter constant associated with the request creation filter. */
const FILTER_CREATION = 3;
/** The request filters preference key. */
const PREF_REQUEST_FILTERS = 'tool_dataprivacy_request-filters';
@ -145,6 +149,34 @@ class helper {
];
}
/**
* Retrieves the human-readable value of a data request creation method.
*
* @param int $creation The request creation method.
* @return string
* @throws moodle_exception
*/
public static function get_request_creation_method_string($creation) {
$creationmethods = self::get_request_creation_methods();
if (!isset($creationmethods[$creation])) {
throw new moodle_exception('errorinvalidrequestcreationmethod', 'tool_dataprivacy');
}
return $creationmethods[$creation];
}
/**
* Returns the key value-pairs of request creation method code and string value.
*
* @return array
*/
public static function get_request_creation_methods() {
return [
data_request::DATAREQUEST_CREATION_MANUAL => get_string('creationmanual', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO => get_string('creationauto', 'tool_dataprivacy'),
];
}
/**
* Get the users that a user can make data request for.
*
@ -199,6 +231,10 @@ class helper {
'name' => get_string('requeststatus', 'tool_dataprivacy'),
'options' => self::get_request_statuses()
],
self::FILTER_CREATION => (object)[
'name' => get_string('requestcreation', 'tool_dataprivacy'),
'options' => self::get_request_creation_methods()
],
];
$options = [];
foreach ($filters as $category => $filtercategory) {

View file

@ -74,15 +74,17 @@ class data_requests_table extends table_sql {
* @param int $userid The user ID
* @param int[] $statuses
* @param int[] $types
* @param int[] $creationmethods
* @param bool $manage
* @throws coding_exception
*/
public function __construct($userid = 0, $statuses = [], $types = [], $manage = false) {
public function __construct($userid = 0, $statuses = [], $types = [], $creationmethods = [], $manage = false) {
parent::__construct('data-requests-table');
$this->userid = $userid;
$this->statuses = $statuses;
$this->types = $types;
$this->creationmethods = $creationmethods;
$this->manage = $manage;
$checkboxattrs = [
@ -273,11 +275,12 @@ class data_requests_table extends table_sql {
$sort = $this->get_sql_sort();
// Get data requests from the given conditions.
$datarequests = api::get_data_requests($this->userid, $this->statuses, $this->types, $sort,
$this->get_page_start(), $this->get_page_size());
$datarequests = api::get_data_requests($this->userid, $this->statuses, $this->types,
$this->creationmethods, $sort, $this->get_page_start(), $this->get_page_size());
// Count data requests from the given conditions.
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types);
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types,
$this->creationmethods);
$this->pagesize($pagesize, $total);
$this->rawdata = [];

View file

@ -167,6 +167,8 @@ class provider implements
$data->type = tool_helper::get_shortened_request_type_string($record->type);
// Status.
$data->status = tool_helper::get_request_status_string($record->status);
// Creation method.
$data->creationmethod = tool_helper::get_request_creation_method_string($record->creationmethod);
// Comments.
$data->comments = $record->comments;
// The DPO's comment about this request.
@ -234,6 +236,10 @@ class provider implements
$option->category = get_string('requeststatus', 'tool_dataprivacy');
$option->name = tool_helper::get_request_status_string($value);
break;
case tool_helper::FILTER_CREATION:
$option->category = get_string('requestcreation', 'tool_dataprivacy');
$option->name = tool_helper::get_request_creation_method_string($value);
break;
}
$descriptions[] = get_string('filteroption', 'tool_dataprivacy', $option);
}

View file

@ -57,8 +57,10 @@ class delete_existing_deleted_users extends scheduled_task {
public function execute() {
global $DB;
// Select all deleted users that do not have any delete data requests created for them.
$sql = "SELECT DISTINCT(u.id)
// Automatic creation of deletion requests must be enabled.
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
// Select all deleted users that do not have any delete data requests created for them.
$sql = "SELECT DISTINCT(u.id)
FROM {user} u
LEFT JOIN {tool_dataprivacy_request} r
ON u.id = r.userid
@ -66,23 +68,24 @@ class delete_existing_deleted_users extends scheduled_task {
AND (r.id IS NULL
OR r.type != ?)";
$params = [
1,
api::DATAREQUEST_TYPE_DELETE
];
$params = [
1,
api::DATAREQUEST_TYPE_DELETE
];
$deletedusers = $DB->get_records_sql($sql, $params);
$createdrequests = 0;
$deletedusers = $DB->get_records_sql($sql, $params);
$createdrequests = 0;
foreach ($deletedusers as $user) {
api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE,
foreach ($deletedusers as $user) {
api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE,
get_string('datarequestcreatedfromscheduledtask', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO);
$createdrequests++;
}
$createdrequests++;
}
if ($createdrequests > 0) {
mtrace($createdrequests . ' delete data request(s) created for existing deleted users');
if ($createdrequests > 0) {
mtrace($createdrequests . ' delete data request(s) created for existing deleted users');
}
}
}
}