mirror of
https://github.com/moodle/moodle.git
synced 2025-04-07 09:23:31 +02:00
Merge branch 'MDL-62211-master' of git://github.com/junpataleta/moodle
This commit is contained in:
commit
af0202c5c2
admin/tool/dataprivacy
1
admin/tool/dataprivacy/amd/build/request_filter.min.js
vendored
Normal file
1
admin/tool/dataprivacy/amd/build/request_filter.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
define(["jquery","core/form-autocomplete","core/str","core/notification"],function(a,b,c,d){var e={REQUEST_FILTERS:"#request-filters"},f=function(){var f=[{key:"filter",component:"moodle"},{key:"nofiltersapplied",component:"moodle"}];c.get_strings(f).then(function(a){var c=a[0],d=a[1];return b.enhance(e.REQUEST_FILTERS,!1,"",c,!1,!0,d,!0)}).fail(d.exception);var g=a(e.REQUEST_FILTERS).val();a(e.REQUEST_FILTERS).on("change",function(){var b=a(this).val();g.join(",")!==b.join(",")&&(0===b.length&&a("#filters-cleared").val(1),a(this.form).submit())})};return{init:function(){f()}}});
|
84
admin/tool/dataprivacy/amd/src/request_filter.js
Normal file
84
admin/tool/dataprivacy/amd/src/request_filter.js
Normal file
@ -0,0 +1,84 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* JS module for the data requests filter.
|
||||
*
|
||||
* @module tool_dataprivacy/request_filter
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'], function($, Autocomplete, Str, Notification) {
|
||||
|
||||
/**
|
||||
* Selectors.
|
||||
*
|
||||
* @access private
|
||||
* @type {{REQUEST_FILTERS: string}}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
REQUEST_FILTERS: '#request-filters'
|
||||
};
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*
|
||||
* @method init
|
||||
* @private
|
||||
*/
|
||||
var init = function() {
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'filter',
|
||||
component: 'moodle'
|
||||
},
|
||||
{
|
||||
key: 'nofiltersapplied',
|
||||
component: 'moodle'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langstrings) {
|
||||
var placeholder = langstrings[0];
|
||||
var noSelectionString = langstrings[1];
|
||||
return Autocomplete.enhance(SELECTORS.REQUEST_FILTERS, false, '', placeholder, false, true, noSelectionString, true);
|
||||
}).fail(Notification.exception);
|
||||
|
||||
var last = $(SELECTORS.REQUEST_FILTERS).val();
|
||||
$(SELECTORS.REQUEST_FILTERS).on('change', function() {
|
||||
var current = $(this).val();
|
||||
// Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
|
||||
if (last.join(',') !== current.join(',')) {
|
||||
// If we're submitting without filters, set the hidden input 'filters-cleared' to 1.
|
||||
if (current.length === 0) {
|
||||
$('#filters-cleared').val(1);
|
||||
}
|
||||
$(this.form).submit();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return /** @alias module:core/form-autocomplete */ {
|
||||
/**
|
||||
* Initialise the unified user filter.
|
||||
*
|
||||
* @method init
|
||||
*/
|
||||
init: function() {
|
||||
init();
|
||||
}
|
||||
};
|
||||
});
|
@ -232,16 +232,42 @@ class api {
|
||||
* (e.g. Users with the Data Protection Officer roles)
|
||||
*
|
||||
* @param int $userid The User ID.
|
||||
* @param int[] $statuses The status filters.
|
||||
* @param int[] $types The request type filters.
|
||||
* @param string $sort The order by clause.
|
||||
* @param int $offset Amount of records to skip.
|
||||
* @param int $limit Amount of records to fetch.
|
||||
* @return data_request[]
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function get_data_requests($userid = 0) {
|
||||
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $sort = '', $offset = 0, $limit = 0) {
|
||||
global $DB, $USER;
|
||||
$results = [];
|
||||
$sort = 'status ASC, timemodified ASC';
|
||||
$sqlparams = [];
|
||||
$sqlconditions = [];
|
||||
|
||||
// Set default sort.
|
||||
if (empty($sort)) {
|
||||
$sort = 'status ASC, timemodified ASC';
|
||||
}
|
||||
|
||||
// Set status filters.
|
||||
if (!empty($statuses)) {
|
||||
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "status $statusinsql";
|
||||
}
|
||||
|
||||
// Set request type filter.
|
||||
if (!empty($types)) {
|
||||
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "type $typeinsql";
|
||||
$sqlparams = array_merge($sqlparams, $typeparams);
|
||||
}
|
||||
|
||||
if ($userid) {
|
||||
// Get the data requests for the user or data requests made by the user.
|
||||
$select = "(userid = :userid OR requestedby = :requestedby)";
|
||||
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'requestedby' => $userid
|
||||
@ -256,20 +282,87 @@ class api {
|
||||
$alloweduserids = array_merge($alloweduserids, array_keys($children));
|
||||
}
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
|
||||
$select .= " AND userid $insql";
|
||||
$params = array_merge($params, $inparams);
|
||||
$sqlconditions[] .= "userid $insql";
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$params = array_merge($params, $inparams, $sqlparams);
|
||||
|
||||
$results = data_request::get_records_select($select, $params, $sort);
|
||||
$results = data_request::get_records_select($select, $params, $sort, '*', $offset, $limit);
|
||||
} else {
|
||||
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
|
||||
if (self::is_site_dpo($USER->id)) {
|
||||
$results = data_request::get_records(null, $sort, '');
|
||||
if (!empty($sqlconditions)) {
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$results = data_request::get_records_select($select, $sqlparams, $sort, '*', $offset, $limit);
|
||||
} else {
|
||||
$results = data_request::get_records(null, $sort, '', $offset, $limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the count of data request records based on the given parameters.
|
||||
*
|
||||
* @param int $userid The User ID.
|
||||
* @param int[] $statuses The status filters.
|
||||
* @param int[] $types The request type filters.
|
||||
* @return int
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function get_data_requests_count($userid = 0, $statuses = [], $types = []) {
|
||||
global $DB, $USER;
|
||||
$count = 0;
|
||||
$sqlparams = [];
|
||||
$sqlconditions = [];
|
||||
if (!empty($statuses)) {
|
||||
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "status $statusinsql";
|
||||
}
|
||||
if (!empty($types)) {
|
||||
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "type $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)";
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'requestedby' => $userid
|
||||
];
|
||||
|
||||
// Build a list of user IDs that the user is allowed to make data requests for.
|
||||
// Of course, the user should be included in this list.
|
||||
$alloweduserids = [$userid];
|
||||
// Get any users that the user can make data requests for.
|
||||
if ($children = helper::get_children_of_user($userid)) {
|
||||
// Get the list of user IDs of the children and merge to the allowed user IDs.
|
||||
$alloweduserids = array_merge($alloweduserids, array_keys($children));
|
||||
}
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] .= "userid $insql";
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$params = array_merge($params, $inparams, $sqlparams);
|
||||
|
||||
$count = data_request::count_records_select($select, $params);
|
||||
} else {
|
||||
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
|
||||
if (self::is_site_dpo($USER->id)) {
|
||||
if (!empty($sqlconditions)) {
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$count = data_request::count_records_select($select, $sqlparams);
|
||||
} else {
|
||||
$count = data_request::count_records();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there is already an existing pending/in-progress data request for a user for a given request type.
|
||||
*
|
||||
|
@ -35,6 +35,17 @@ use tool_dataprivacy\api;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
/** The default number of results to be shown per page. */
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
/** Filter constant associated with the request type filter. */
|
||||
const FILTER_TYPE = 1;
|
||||
|
||||
/** Filter constant associated with the request status filter. */
|
||||
const FILTER_STATUS = 2;
|
||||
|
||||
/** The request filters preference key. */
|
||||
const PREF_REQUEST_FILTERS = 'tool_dataprivacy_request-filters';
|
||||
|
||||
/**
|
||||
* Retrieves the human-readable text value of a data request type.
|
||||
@ -45,16 +56,11 @@ class helper {
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_request_type_string($requesttype) {
|
||||
switch ($requesttype) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
return get_string('requesttypeexport', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_DELETE:
|
||||
return get_string('requesttypedelete', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_OTHERS:
|
||||
return get_string('requesttypeothers', 'tool_dataprivacy');
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
$types = self::get_request_types();
|
||||
if (!isset($types[$requesttype])) {
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
return $types[$requesttype];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,16 +72,37 @@ class helper {
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_shortened_request_type_string($requesttype) {
|
||||
switch ($requesttype) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
return get_string('requesttypeexportshort', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_DELETE:
|
||||
return get_string('requesttypedeleteshort', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_OTHERS:
|
||||
return get_string('requesttypeothersshort', 'tool_dataprivacy');
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
$types = self::get_request_types_short();
|
||||
if (!isset($types[$requesttype])) {
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
return $types[$requesttype];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request type code and their string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_types() {
|
||||
return [
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothers', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request type code and their shortened string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_types_short() {
|
||||
return [
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexportshort', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedeleteshort', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothersshort', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,30 +110,32 @@ class helper {
|
||||
*
|
||||
* @param int $status The request status.
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_request_status_string($status) {
|
||||
switch ($status) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
return get_string('statuspending', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_PREPROCESSING:
|
||||
return get_string('statuspreprocessing', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
|
||||
return get_string('statusawaitingapproval', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_APPROVED:
|
||||
return get_string('statusapproved', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_PROCESSING:
|
||||
return get_string('statusprocessing', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_COMPLETE:
|
||||
return get_string('statuscomplete', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
return get_string('statuscancelled', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
return get_string('statusrejected', 'tool_dataprivacy');
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
|
||||
$statuses = self::get_request_statuses();
|
||||
if (!isset($statuses[$status])) {
|
||||
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
|
||||
}
|
||||
return $statuses[$status];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request status code and string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_statuses() {
|
||||
return [
|
||||
api::DATAREQUEST_STATUS_PENDING => get_string('statuspending', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PREPROCESSING => get_string('statuspreprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => get_string('statusawaitingapproval', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_APPROVED => get_string('statusapproved', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PROCESSING => get_string('statusprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_COMPLETE => get_string('statuscomplete', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_CANCELLED => get_string('statuscancelled', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_REJECTED => get_string('statusrejected', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,4 +175,34 @@ class helper {
|
||||
}
|
||||
return $finalresults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options for the data requests filter.
|
||||
*
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function get_request_filter_options() {
|
||||
$filters = [
|
||||
self::FILTER_TYPE => (object)[
|
||||
'name' => get_string('requesttype', 'tool_dataprivacy'),
|
||||
'options' => self::get_request_types_short()
|
||||
],
|
||||
self::FILTER_STATUS => (object)[
|
||||
'name' => get_string('requeststatus', 'tool_dataprivacy'),
|
||||
'options' => self::get_request_statuses()
|
||||
],
|
||||
];
|
||||
$options = [];
|
||||
foreach ($filters as $category => $filtercategory) {
|
||||
foreach ($filtercategory->options as $key => $name) {
|
||||
$option = (object)[
|
||||
'category' => $filtercategory->name,
|
||||
'name' => $name
|
||||
];
|
||||
$options["{$category}:{$key}"] = get_string('filteroption', 'tool_dataprivacy', $option);
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use dml_exception;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
@ -34,7 +33,7 @@ use single_select;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\output\expired_contexts_table;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
/**
|
||||
* Class containing data for a user's data requests.
|
||||
@ -44,9 +43,6 @@ use tool_dataprivacy\output\expired_contexts_table;
|
||||
*/
|
||||
class data_deletion_page implements renderable, templatable {
|
||||
|
||||
/** The default number of results to be shown per page. */
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
/** @var data_request[] $requests List of data requests. */
|
||||
protected $filter = null;
|
||||
|
||||
@ -57,7 +53,7 @@ class data_deletion_page implements renderable, templatable {
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param \tool_dataprivacy\data_request[] $filter
|
||||
* @param \tool_dataprivacy\expired_contexts_table $expiredcontextstable
|
||||
* @param expired_contexts_table $expiredcontextstable
|
||||
*/
|
||||
public function __construct($filter, expired_contexts_table $expiredcontextstable) {
|
||||
$this->filter = $filter;
|
||||
@ -70,7 +66,6 @@ class data_deletion_page implements renderable, templatable {
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
@ -87,7 +82,7 @@ class data_deletion_page implements renderable, templatable {
|
||||
$data->filter = $filterselector->export_for_template($output);
|
||||
|
||||
ob_start();
|
||||
$this->expiredcontextstable->out(self::DEFAULT_PAGE_SIZE, true);
|
||||
$this->expiredcontextstable->out(helper::DEFAULT_PAGE_SIZE, true);
|
||||
$expiredcontexts = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$data->expiredcontexts = $expiredcontexts;
|
||||
|
@ -24,20 +24,17 @@
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use action_menu;
|
||||
use action_menu_link_secondary;
|
||||
use coding_exception;
|
||||
use context_system;
|
||||
use dml_exception;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use single_select;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
/**
|
||||
* Class containing data for a user's data requests.
|
||||
@ -47,16 +44,21 @@ use tool_dataprivacy\external\data_request_exporter;
|
||||
*/
|
||||
class data_requests_page implements renderable, templatable {
|
||||
|
||||
/** @var data_request[] $requests List of data requests. */
|
||||
protected $requests = [];
|
||||
/** @var data_requests_table $table The data requests table. */
|
||||
protected $table;
|
||||
|
||||
/** @var int[] $filters The applied filters. */
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param data_request[] $requests
|
||||
* @param data_requests_table $table The data requests table.
|
||||
* @param int[] $filters The applied filters.
|
||||
*/
|
||||
public function __construct($requests) {
|
||||
$this->requests = $requests;
|
||||
public function __construct($table, $filters) {
|
||||
$this->table = $table;
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,43 +80,17 @@ class data_requests_page implements renderable, templatable {
|
||||
$data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
|
||||
}
|
||||
|
||||
$requests = [];
|
||||
foreach ($this->requests as $request) {
|
||||
$requestid = $request->get('id');
|
||||
$status = $request->get('status');
|
||||
$requestexporter = new data_request_exporter($request, ['context' => context_system::instance()]);
|
||||
$item = $requestexporter->export($output);
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
|
||||
$filteroptions = helper::get_request_filter_options();
|
||||
$filter = new request_filter($filteroptions, $this->filters, $url);
|
||||
$data->filter = $filter->export_for_template($output);
|
||||
|
||||
// Prepare actions.
|
||||
$actions = [];
|
||||
ob_start();
|
||||
$this->table->out(helper::DEFAULT_PAGE_SIZE, true);
|
||||
$requests = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// View action.
|
||||
$actionurl = new moodle_url('#');
|
||||
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
|
||||
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
if ($status == api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
|
||||
// Approve.
|
||||
$actiondata['data-action'] = 'approve';
|
||||
$actiontext = get_string('approverequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
// Deny.
|
||||
$actiondata['data-action'] = 'deny';
|
||||
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
}
|
||||
|
||||
$actionsmenu = new action_menu($actions);
|
||||
$actionsmenu->set_menu_trigger(get_string('actions'));
|
||||
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
|
||||
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
$item->actions = $actionsmenu->export_for_template($output);
|
||||
|
||||
$requests[] = $item;
|
||||
}
|
||||
$data->requests = $requests;
|
||||
$data->datarequests = $requests;
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
262
admin/tool/dataprivacy/classes/output/data_requests_table.php
Normal file
262
admin/tool/dataprivacy/classes/output/data_requests_table.php
Normal file
@ -0,0 +1,262 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the class used for the displaying the data requests table.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/tablelib.php');
|
||||
|
||||
use action_menu;
|
||||
use action_menu_link_secondary;
|
||||
use coding_exception;
|
||||
use dml_exception;
|
||||
use html_writer;
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
use table_sql;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
/**
|
||||
* The class for displaying the data requests table.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_requests_table extends table_sql {
|
||||
|
||||
/** @var int The user ID. */
|
||||
protected $userid = 0;
|
||||
|
||||
/** @var int[] The status filters. */
|
||||
protected $statuses = [];
|
||||
|
||||
/** @var int[] The request type filters. */
|
||||
protected $types = [];
|
||||
|
||||
/** @var bool Whether this table is being rendered for managing data requests. */
|
||||
protected $manage = false;
|
||||
|
||||
/** @var stdClass[] Array of data request persistents. */
|
||||
protected $datarequests = [];
|
||||
|
||||
/**
|
||||
* data_requests_table constructor.
|
||||
*
|
||||
* @param int $userid The user ID
|
||||
* @param int[] $statuses
|
||||
* @param int[] $types
|
||||
* @param bool $manage
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function __construct($userid = 0, $statuses = [], $types = [], $manage = false) {
|
||||
parent::__construct('data-requests-table');
|
||||
|
||||
$this->userid = $userid;
|
||||
$this->statuses = $statuses;
|
||||
$this->types = $types;
|
||||
$this->manage = $manage;
|
||||
|
||||
$columnheaders = [
|
||||
'type' => get_string('requesttype', 'tool_dataprivacy'),
|
||||
'userid' => get_string('user', 'tool_dataprivacy'),
|
||||
'timecreated' => get_string('daterequested', 'tool_dataprivacy'),
|
||||
'requestedby' => get_string('requestby', 'tool_dataprivacy'),
|
||||
'status' => get_string('requeststatus', 'tool_dataprivacy'),
|
||||
'comments' => get_string('message', 'tool_dataprivacy'),
|
||||
'actions' => '',
|
||||
];
|
||||
|
||||
$this->define_columns(array_keys($columnheaders));
|
||||
$this->define_headers(array_values($columnheaders));
|
||||
$this->no_sorting('actions');
|
||||
}
|
||||
|
||||
/**
|
||||
* The type column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_type($data) {
|
||||
if ($this->manage) {
|
||||
return $data->typenameshort;
|
||||
}
|
||||
return $data->typename;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_userid($data) {
|
||||
$user = $data->foruser;
|
||||
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The context information column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_timecreated($data) {
|
||||
return userdate($data->timecreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* The requesting user's column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_requestedby($data) {
|
||||
$user = $data->requestedbyuser;
|
||||
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The status column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_status($data) {
|
||||
return html_writer::span($data->statuslabel, 'label ' . $data->statuslabelclass);
|
||||
}
|
||||
|
||||
/**
|
||||
* The comments column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_comments($data) {
|
||||
return shorten_text($data->comments, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actions column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_actions($data) {
|
||||
global $OUTPUT;
|
||||
|
||||
$requestid = $data->id;
|
||||
$status = $data->status;
|
||||
|
||||
// Prepare actions.
|
||||
$actions = [];
|
||||
|
||||
// View action.
|
||||
$actionurl = new moodle_url('#');
|
||||
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
|
||||
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
if ($status == api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
|
||||
// Approve.
|
||||
$actiondata['data-action'] = 'approve';
|
||||
$actiontext = get_string('approverequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
// Deny.
|
||||
$actiondata['data-action'] = 'deny';
|
||||
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
}
|
||||
|
||||
$actionsmenu = new action_menu($actions);
|
||||
$actionsmenu->set_menu_trigger(get_string('actions'));
|
||||
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
|
||||
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
|
||||
return $OUTPUT->render($actionsmenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database for results to display in the table.
|
||||
*
|
||||
* @param int $pagesize size of page for paginated displayed table.
|
||||
* @param bool $useinitialsbar do you want to use the initials bar.
|
||||
* @throws dml_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function query_db($pagesize, $useinitialsbar = true) {
|
||||
global $PAGE;
|
||||
|
||||
// Count data requests from the given conditions.
|
||||
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types);
|
||||
$this->pagesize($pagesize, $total);
|
||||
|
||||
$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());
|
||||
$this->rawdata = [];
|
||||
$context = \context_system::instance();
|
||||
$renderer = $PAGE->get_renderer('tool_dataprivacy');
|
||||
foreach ($datarequests as $persistent) {
|
||||
$exporter = new data_request_exporter($persistent, ['context' => $context]);
|
||||
$this->rawdata[] = $exporter->export($renderer);
|
||||
}
|
||||
|
||||
// Set initial bars.
|
||||
if ($useinitialsbar) {
|
||||
$this->initialbars($total > $pagesize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override default implementation to display a more meaningful information to the user.
|
||||
*/
|
||||
public function print_nothing_to_display() {
|
||||
global $OUTPUT;
|
||||
echo $this->render_reset_button();
|
||||
$this->print_initials_bar();
|
||||
if (!empty($this->statuses) || !empty($this->types)) {
|
||||
$message = get_string('nodatarequestsmatchingfilter', 'tool_dataprivacy');
|
||||
} else {
|
||||
$message = get_string('nodatarequests', 'tool_dataprivacy');
|
||||
}
|
||||
echo $OUTPUT->notification($message, 'warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the table's show_hide_link method to prevent the show/hide links from rendering.
|
||||
*
|
||||
* @param string $column the column name, index into various names.
|
||||
* @param int $index numerical index of the column.
|
||||
* @return string HTML fragment.
|
||||
*/
|
||||
protected function show_hide_link($column, $index) {
|
||||
return '';
|
||||
}
|
||||
}
|
98
admin/tool/dataprivacy/classes/output/request_filter.php
Normal file
98
admin/tool/dataprivacy/classes/output/request_filter.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Class containing the filter options data for rendering the autocomplete element for the data requests page.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class containing the filter options data for rendering the autocomplete element for the data requests page.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class request_filter implements renderable, templatable {
|
||||
|
||||
/** @var array $filteroptions The filter options. */
|
||||
protected $filteroptions;
|
||||
|
||||
/** @var array $selectedoptions The list of selected filter option values. */
|
||||
protected $selectedoptions;
|
||||
|
||||
/** @var moodle_url|string $baseurl The url with params needed to call up this page. */
|
||||
protected $baseurl;
|
||||
|
||||
/**
|
||||
* request_filter constructor.
|
||||
*
|
||||
* @param array $filteroptions The filter options.
|
||||
* @param array $selectedoptions The list of selected filter option values.
|
||||
* @param string|moodle_url $baseurl The url with params needed to call up this page.
|
||||
*/
|
||||
public function __construct($filteroptions, $selectedoptions, $baseurl = null) {
|
||||
$this->filteroptions = $filteroptions;
|
||||
$this->selectedoptions = $selectedoptions;
|
||||
if (!empty($baseurl)) {
|
||||
$this->baseurl = new moodle_url($baseurl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to export the renderer data in a format that is suitable for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
|
||||
* @return stdClass|array
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $PAGE;
|
||||
$data = new stdClass();
|
||||
if (empty($this->baseurl)) {
|
||||
$this->baseurl = $PAGE->url;
|
||||
}
|
||||
$data->action = $this->baseurl->out(false);
|
||||
|
||||
foreach ($this->selectedoptions as $option) {
|
||||
if (!isset($this->filteroptions[$option])) {
|
||||
$this->filteroptions[$option] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
$data->filteroptions = [];
|
||||
foreach ($this->filteroptions as $value => $label) {
|
||||
$selected = in_array($value, $this->selectedoptions);
|
||||
$filteroption = (object)[
|
||||
'value' => $value,
|
||||
'label' => $label
|
||||
];
|
||||
$filteroption->selected = $selected;
|
||||
$data->filteroptions[] = $filteroption;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -51,7 +51,10 @@ class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
|
||||
// This tool may provide access to and deletion of user data.
|
||||
\core_privacy\local\request\plugin\provider {
|
||||
\core_privacy\local\request\plugin\provider,
|
||||
|
||||
// This plugin has some sitewide user preferences to export.
|
||||
\core_privacy\local\request\user_preference_provider {
|
||||
/**
|
||||
* Returns meta data about this system.
|
||||
*
|
||||
@ -70,6 +73,10 @@ class provider implements
|
||||
],
|
||||
'privacy:metadata:request'
|
||||
);
|
||||
|
||||
$collection->add_user_preference(tool_helper::PREF_REQUEST_FILTERS,
|
||||
'privacy:metadata:preference:tool_dataprivacy_request-filters');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
@ -162,4 +169,36 @@ class provider implements
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
*
|
||||
* @param int $userid The userid of the user whose data is to be exported.
|
||||
*/
|
||||
public static function export_user_preferences(int $userid) {
|
||||
$preffilter = get_user_preferences(tool_helper::PREF_REQUEST_FILTERS, null, $userid);
|
||||
if ($preffilter !== null) {
|
||||
$filters = json_decode($preffilter);
|
||||
$descriptions = [];
|
||||
foreach ($filters as $filter) {
|
||||
list($category, $value) = explode(':', $filter);
|
||||
$option = new stdClass();
|
||||
switch($category) {
|
||||
case tool_helper::FILTER_TYPE:
|
||||
$option->category = get_string('requesttype', 'tool_dataprivacy');
|
||||
$option->name = tool_helper::get_shortened_request_type_string($value);
|
||||
break;
|
||||
case tool_helper::FILTER_STATUS:
|
||||
$option->category = get_string('requeststatus', 'tool_dataprivacy');
|
||||
$option->name = tool_helper::get_request_status_string($value);
|
||||
break;
|
||||
}
|
||||
$descriptions[] = get_string('filteroption', 'tool_dataprivacy', $option);
|
||||
}
|
||||
// Export the filter preference as comma-separated values and text descriptions.
|
||||
$values = implode(', ', $filters);
|
||||
$descriptionstext = implode(', ', $descriptions);
|
||||
writer::export_user_preference('tool_dataprivacy', tool_helper::PREF_REQUEST_FILTERS, $values, $descriptionstext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,38 @@ $title = get_string('datarequests', 'tool_dataprivacy');
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($title);
|
||||
|
||||
$requests = tool_dataprivacy\api::get_data_requests();
|
||||
$requestlist = new tool_dataprivacy\output\data_requests_page($requests);
|
||||
$filtersapplied = optional_param_array('request-filters', [-1], PARAM_NOTAGS);
|
||||
$filterscleared = optional_param('filters-cleared', 0, PARAM_INT);
|
||||
if ($filtersapplied === [-1]) {
|
||||
// If there are no filters submitted, check if there is a saved filters from the user preferences.
|
||||
$filterprefs = get_user_preferences(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, null);
|
||||
if ($filterprefs && empty($filterscleared)) {
|
||||
$filtersapplied = json_decode($filterprefs);
|
||||
} else {
|
||||
$filtersapplied = [];
|
||||
}
|
||||
}
|
||||
// Save the current applied filters to the user preferences.
|
||||
set_user_preference(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, json_encode($filtersapplied));
|
||||
|
||||
$types = [];
|
||||
$statuses = [];
|
||||
foreach ($filtersapplied as $filter) {
|
||||
list($category, $value) = explode(':', $filter);
|
||||
switch($category) {
|
||||
case \tool_dataprivacy\local\helper::FILTER_TYPE:
|
||||
$types[] = $value;
|
||||
break;
|
||||
case \tool_dataprivacy\local\helper::FILTER_STATUS:
|
||||
$statuses[] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$table = new \tool_dataprivacy\output\data_requests_table(0, $statuses, $types, true);
|
||||
$table->baseurl = $url;
|
||||
|
||||
$requestlist = new tool_dataprivacy\output\data_requests_page($table, $filtersapplied);
|
||||
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $requestlistoutput->render($requestlist);
|
||||
|
||||
|
@ -112,6 +112,7 @@ $string['expandplugintype'] = 'Expand and collapse plugin type.';
|
||||
$string['explanationtitle'] = 'Icons used on this page and what they mean.';
|
||||
$string['external'] = 'Additional';
|
||||
$string['externalexplanation'] = 'An additional plugin installed on this site.';
|
||||
$string['filteroption'] = '{$a->category}: {$a->name}';
|
||||
$string['frontpagecourse'] = 'Front page course';
|
||||
$string['gdpr_art_6_1_a_description'] = 'The data subject has given consent to the processing of his or her personal data for one or more specific purposes';
|
||||
$string['gdpr_art_6_1_a_name'] = 'Consent (GDPR Art. 6.1(a))';
|
||||
@ -162,6 +163,7 @@ $string['nameemail'] = '{$a->name} ({$a->email})';
|
||||
$string['nchildren'] = '{$a} children';
|
||||
$string['newrequest'] = 'New request';
|
||||
$string['nodatarequests'] = 'There are no data requests';
|
||||
$string['nodatarequestsmatchingfilter'] = 'There are no data requests matching the given filter';
|
||||
$string['noactivitiestoload'] = 'No activities';
|
||||
$string['noassignedroles'] = 'No assigned roles in this context';
|
||||
$string['noblockstoload'] = 'No blocks';
|
||||
@ -176,6 +178,7 @@ $string['notset'] = 'Not set (use the default value)';
|
||||
$string['pluginregistry'] = 'Plugin privacy registry';
|
||||
$string['pluginregistrytitle'] = 'Plugin privacy compliance registry';
|
||||
$string['privacy'] = 'Privacy';
|
||||
$string['privacy:metadata:preference:tool_dataprivacy_request-filters'] = 'The filters currently applied to the data requests page.';
|
||||
$string['privacy:metadata:request'] = 'Information from personal data requests (subject access and deletion requests) made for this site.';
|
||||
$string['privacy:metadata:request:comments'] = 'Any user comments accompanying the request.';
|
||||
$string['privacy:metadata:request:userid'] = 'The ID of the user to whom the request belongs';
|
||||
|
@ -26,92 +26,32 @@
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* requests - Array of data requests.
|
||||
* newdatarequesturl string The URL pointing to the data request creation page.
|
||||
* datarequests string The HTML of the data requests table.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"requests": [
|
||||
{
|
||||
"id": 1,
|
||||
"foruser" : {
|
||||
"fullname": "Oscar Olsen",
|
||||
"profileurl": "#"
|
||||
"newdatarequesturl": "#",
|
||||
"datarequests": "<table><tr><td>This is the table where the list of data requests will be rendered</td></tr></table>",
|
||||
"filter": {
|
||||
"action": "#",
|
||||
"filteroptions": [
|
||||
{
|
||||
"value": "1",
|
||||
"label": "Option 1"
|
||||
},
|
||||
"typenameshort" : "Export",
|
||||
"comments": "I would like to download all of my daughter's personal data",
|
||||
"statuslabelclass": "label-default",
|
||||
"statuslabel": "Pending",
|
||||
"timecreated" : 1517902435,
|
||||
"requestedbyuser" : {
|
||||
"fullname": "Martha Smith",
|
||||
"profileurl": "#"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"foruser" : {
|
||||
"fullname": "Alexandre Denys",
|
||||
"profileurl": "#"
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Option 2",
|
||||
"selected": true
|
||||
},
|
||||
"typenameshort" : "Export",
|
||||
"comments": "Please give me all of the information you have about me...",
|
||||
"statuslabelclass": "label-warning",
|
||||
"statuslabel": "Awaiting completion",
|
||||
"timecreated" : 1517902435,
|
||||
"requestedbyuser" : {
|
||||
"fullname": "Martha Smith",
|
||||
"profileurl": "#"
|
||||
{
|
||||
"value": "3",
|
||||
"label": "Option 3",
|
||||
"selected": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"foruser" : {
|
||||
"fullname": "Hirondino Moura",
|
||||
"profileurl": "#"
|
||||
},
|
||||
"typenameshort" : "Delete",
|
||||
"comments": "Please delete all of my son's personal data.",
|
||||
"statuslabelclass": "label-success",
|
||||
"statuslabel": "Complete",
|
||||
"timecreated" : 1517902435,
|
||||
"requestedbyuser" : {
|
||||
"fullname": "Martha Smith",
|
||||
"profileurl": "#"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"foruser" : {
|
||||
"fullname": "Florian Krause",
|
||||
"profileurl": "#"
|
||||
},
|
||||
"typenameshort" : "Delete",
|
||||
"comments": "I would like to request for my personal data to be deleted from your site. Thanks!",
|
||||
"statuslabelclass": "label-danger",
|
||||
"statuslabel": "Rejected",
|
||||
"timecreated" : 1517902435,
|
||||
"requestedbyuser" : {
|
||||
"fullname": "Martha Smith",
|
||||
"profileurl": "#"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"foruser" : {
|
||||
"fullname": "Nicklas Sørensen",
|
||||
"profileurl": "#"
|
||||
},
|
||||
"typenameshort" : "Export",
|
||||
"comments": "Please let me download my data",
|
||||
"statuslabelclass": "label-info",
|
||||
"statuslabel": "Processing",
|
||||
"timecreated" : 1517902435,
|
||||
"requestedbyuser" : {
|
||||
"fullname": "Martha Smith",
|
||||
"profileurl": "#"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@ -121,53 +61,19 @@
|
||||
|
||||
<div data-region="datarequests">
|
||||
<div class="m-t-1 m-b-1">
|
||||
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
|
||||
{{#str}}newrequest, tool_dataprivacy{{/str}}
|
||||
</a>
|
||||
<div class="pull-right">
|
||||
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
|
||||
{{#str}}newrequest, tool_dataprivacy{{/str}}
|
||||
</a>
|
||||
</div>
|
||||
{{#filter}}
|
||||
{{>tool_dataprivacy/request_filter}}
|
||||
{{/filter}}
|
||||
</div>
|
||||
|
||||
<div class="m-t-1 m-b-1" data-region="data-requests-table">
|
||||
{{{datarequests}}}
|
||||
</div>
|
||||
<table class="generaltable fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{#str}}requesttype, tool_dataprivacy{{/str}}</th>
|
||||
<th scope="col">{{#str}}user, tool_dataprivacy{{/str}}</th>
|
||||
<th scope="col">{{#str}}daterequested, tool_dataprivacy{{/str}}</th>
|
||||
<th scope="col">{{#str}}requestby, tool_dataprivacy{{/str}}</th>
|
||||
<th scope="col">{{#str}}requeststatus, tool_dataprivacy{{/str}}</th>
|
||||
<th scope="col" colspan="2">{{#str}}message, tool_dataprivacy{{/str}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#requests}}
|
||||
<tr {{!
|
||||
}} data-region="request-node"{{!
|
||||
}} data-id="{{id}}"{{!
|
||||
}} data-type="{{type}}"{{!
|
||||
}} data-status="{{status}}"{{!
|
||||
}}>
|
||||
<td>{{typenameshort}}</td>
|
||||
<td><a href="{{foruser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{foruser.fullname}}</a></td>
|
||||
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}</td>
|
||||
<td><a href="{{requestedbyuser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{requestedbyuser.fullname}}</a></td>
|
||||
<td>
|
||||
<span class="label {{statuslabelclass}}">{{statuslabel}}</span>
|
||||
</td>
|
||||
<td>{{#shortentext}}60, {{comments}}{{/shortentext}}</td>
|
||||
<td>
|
||||
{{#actions}}
|
||||
{{> core/action_menu}}
|
||||
{{/actions}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/requests}}
|
||||
{{^requests}}
|
||||
<tr>
|
||||
<td class="text-muted" colspan="6">
|
||||
{{#str}}nodatarequests, tool_dataprivacy{{/str}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/requests}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{#js}}
|
||||
|
67
admin/tool/dataprivacy/templates/request_filter.mustache
Normal file
67
admin/tool/dataprivacy/templates/request_filter.mustache
Normal file
@ -0,0 +1,67 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template tool_dataprivacy/request_filter
|
||||
|
||||
Template for the request filter element.
|
||||
|
||||
Context variables required for this template:
|
||||
* action string - The action URL for the form.
|
||||
* filteroptions - Array of filter options.
|
||||
* value string - The option value.
|
||||
* label string - The option label.
|
||||
* selected boolean - Whether the option is selected
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"action": "#",
|
||||
"filteroptions": [
|
||||
{
|
||||
"value": "1",
|
||||
"label": "Option 1"
|
||||
},
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Option 2",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"value": "3",
|
||||
"label": "Option 3",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"value": "4",
|
||||
"label": "Option 4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<form method="post" action="{{action}}" class="m-b-1" role="search" id="request_filter_form">
|
||||
<label for="request-filters" class="sr-only">{{#str}}filters{{/str}}</label>
|
||||
<select name="request-filters[]" id="request-filters" multiple="multiple" class="form-autocomplete-original-select">
|
||||
{{#filteroptions}}
|
||||
<option value="{{value}}" {{#selected}}selected="selected"{{/selected}}>{{{label}}}</option>
|
||||
{{/filteroptions}}
|
||||
</select>
|
||||
<input type="hidden" id="filters-cleared" name="filters-cleared" value="0" />
|
||||
</form>
|
||||
{{#js}}
|
||||
require(['tool_dataprivacy/request_filter'], function(Filter) {
|
||||
Filter.init();
|
||||
});
|
||||
{{/js}}
|
@ -29,6 +29,7 @@ use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_registry;
|
||||
use tool_dataprivacy\expired_context;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\local\helper;
|
||||
use tool_dataprivacy\task\initiate_data_request_task;
|
||||
use tool_dataprivacy\task\process_data_request_task;
|
||||
|
||||
@ -411,42 +412,128 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::get_data_requests()
|
||||
* Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_get_data_requests() {
|
||||
public function get_data_requests_provider() {
|
||||
$generator = new testing_data_generator();
|
||||
$user1 = $generator->create_user();
|
||||
$user2 = $generator->create_user();
|
||||
$comment = 'sample comment';
|
||||
$user3 = $generator->create_user();
|
||||
$user4 = $generator->create_user();
|
||||
$user5 = $generator->create_user();
|
||||
$users = [$user1, $user2, $user3, $user4, $user5];
|
||||
$completeonly = [api::DATAREQUEST_STATUS_COMPLETE];
|
||||
$completeandcancelled = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_CANCELLED];
|
||||
|
||||
// Make a data request as user 1.
|
||||
$this->setUser($user1);
|
||||
$d1 = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
||||
// Make a data request as user 2.
|
||||
$this->setUser($user2);
|
||||
$d2 = api::create_data_request($user2->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
||||
return [
|
||||
// Own data requests.
|
||||
[$users, $user1, false, $completeonly],
|
||||
// Non-DPO fetching all requets.
|
||||
[$users, $user2, true, $completeonly],
|
||||
// Admin fetching all completed and cancelled requests.
|
||||
[$users, get_admin(), true, $completeandcancelled],
|
||||
// Admin fetching all completed requests.
|
||||
[$users, get_admin(), true, $completeonly],
|
||||
// Guest fetching all requests.
|
||||
[$users, guest_user(), true, $completeonly],
|
||||
];
|
||||
}
|
||||
|
||||
// Fetching data requests of specific users.
|
||||
$requests = api::get_data_requests($user1->id);
|
||||
$this->assertCount(1, $requests);
|
||||
$datarequest = reset($requests);
|
||||
$this->assertEquals($d1->to_record(), $datarequest->to_record());
|
||||
/**
|
||||
* Test for api::get_data_requests()
|
||||
*
|
||||
* @dataProvider get_data_requests_provider
|
||||
* @param stdClass[] $users Array of users to create data requests for.
|
||||
* @param stdClass $loggeduser The user logging in.
|
||||
* @param boolean $fetchall Whether to fetch all records.
|
||||
* @param int[] $statuses Status filters.
|
||||
*/
|
||||
public function test_get_data_requests($users, $loggeduser, $fetchall, $statuses) {
|
||||
$comment = 'Data %s request comment by user %d';
|
||||
$exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
|
||||
$deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
|
||||
// Make a data requests for the users.
|
||||
foreach ($users as $user) {
|
||||
$this->setUser($user);
|
||||
api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
|
||||
api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
|
||||
}
|
||||
|
||||
$requests = api::get_data_requests($user2->id);
|
||||
$this->assertCount(1, $requests);
|
||||
$datarequest = reset($requests);
|
||||
$this->assertEquals($d2->to_record(), $datarequest->to_record());
|
||||
// Log in as the target user.
|
||||
$this->setUser($loggeduser);
|
||||
// Get records count based on the filters.
|
||||
$userid = $loggeduser->id;
|
||||
if ($fetchall) {
|
||||
$userid = 0;
|
||||
}
|
||||
$count = api::get_data_requests_count($userid);
|
||||
if (api::is_site_dpo($loggeduser->id)) {
|
||||
// DPOs should see all the requests.
|
||||
$this->assertEquals(count($users) * 2, $count);
|
||||
} else {
|
||||
if (empty($userid)) {
|
||||
// There should be no data requests for this user available.
|
||||
$this->assertEquals(0, $count);
|
||||
} else {
|
||||
// There should be only one (request with pending status).
|
||||
$this->assertEquals(2, $count);
|
||||
}
|
||||
}
|
||||
// Get data requests.
|
||||
$requests = api::get_data_requests($userid);
|
||||
// The number of requests should match the count.
|
||||
$this->assertCount($count, $requests);
|
||||
|
||||
// Fetching data requests of all users.
|
||||
// As guest.
|
||||
$this->setGuestUser();
|
||||
$requests = api::get_data_requests();
|
||||
$this->assertEmpty($requests);
|
||||
// Test filtering by status.
|
||||
if ($count && !empty($statuses)) {
|
||||
$filteredcount = api::get_data_requests_count($userid, $statuses);
|
||||
// There should be none as they are all pending.
|
||||
$this->assertEquals(0, $filteredcount);
|
||||
$filteredrequests = api::get_data_requests($userid, $statuses);
|
||||
$this->assertCount($filteredcount, $filteredrequests);
|
||||
|
||||
// As DPO (admin in this case, which is default if no site DPOs are set).
|
||||
$this->setAdminUser();
|
||||
$requests = api::get_data_requests();
|
||||
$this->assertCount(2, $requests);
|
||||
$statuscounts = [];
|
||||
foreach ($statuses as $stat) {
|
||||
$statuscounts[$stat] = 0;
|
||||
}
|
||||
$numstatus = count($statuses);
|
||||
// Get all requests with status filter and update statuses, randomly.
|
||||
foreach ($requests as $request) {
|
||||
if (rand(0, 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($numstatus > 1) {
|
||||
$index = rand(0, $numstatus - 1);
|
||||
$status = $statuses[$index];
|
||||
} else {
|
||||
$status = reset($statuses);
|
||||
}
|
||||
$statuscounts[$status]++;
|
||||
api::update_request_status($request->get('id'), $status);
|
||||
}
|
||||
$total = array_sum($statuscounts);
|
||||
$filteredcount = api::get_data_requests_count($userid, $statuses);
|
||||
$this->assertEquals($total, $filteredcount);
|
||||
$filteredrequests = api::get_data_requests($userid, $statuses);
|
||||
$this->assertCount($filteredcount, $filteredrequests);
|
||||
// Confirm the filtered requests match the status filter(s).
|
||||
foreach ($filteredrequests as $request) {
|
||||
$this->assertContains($request->get('status'), $statuses);
|
||||
}
|
||||
|
||||
if ($numstatus > 1) {
|
||||
// Fetch by individual status to check the numbers match.
|
||||
foreach ($statuses as $status) {
|
||||
$filteredcount = api::get_data_requests_count($userid, [$status]);
|
||||
$this->assertEquals($statuscounts[$status], $filteredcount);
|
||||
$filteredrequests = api::get_data_requests($userid, [$status]);
|
||||
$this->assertCount($filteredcount, $filteredrequests);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user