Merge branch 'MDL-62554-master' of git://github.com/junpataleta/moodle

This commit is contained in:
David Monllao
2018-09-17 13:44:49 +02:00
24 changed files with 2528 additions and 138 deletions
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){function h(b,d,h,j,k,l,m,n){null!==h&&l.forEach(function(a){a.id===h&&(a.selected=!0)}),null!==j&&m.forEach(function(a){a.id===j&&(a.selected=!0)});var o={contextlevel:d,categoryoptions:l,purposeoptions:m};null!==n&&n.length&&(null===k?o.newactivitydefaults=!0:n.forEach(function(a){k===a.name&&(a.selected=!0)}),o.modemodule=!0,o.activityoptions=n),e.create({title:b,body:g.render("tool_dataprivacy/category_purpose_form",o),type:e.types.SAVE_CANCEL,large:!0}).then(function(b){return b.getRoot().on(f.save,function(){var b=a("#activity"),c="undefined"!=typeof b?b.val():null,d=a("#override"),e="undefined"!=typeof d&&d.is(":checked");i(a("#contextlevel").val(),a("#category").val(),a("#purpose").val(),c,e)}),b.getRoot().on(f.hidden,function(){b.destroy()}),b.show(),b})["catch"](c.exception)}function i(a,c,d,e,f){var g={methodname:"tool_dataprivacy_set_context_defaults",args:{contextlevel:a,category:c,purpose:d,override:f,activity:e}};b.call([g])[0].done(function(a){a.result&&window.location.reload()})}var j={EDIT_LEVEL_DEFAULTS:'[data-action="edit-level-defaults"]',NEW_ACTIVITY_DEFAULTS:'[data-action="new-activity-defaults"]',EDIT_ACTIVITY_DEFAULTS:'[data-action="edit-activity-defaults"]',DELETE_ACTIVITY_DEFAULTS:'[data-action="delete-activity-defaults"]'},k=-1,l=function(){this.registerEvents()};return l.prototype.registerEvents=function(){a(j.EDIT_LEVEL_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=f.data("category"),j=f.data("purpose"),k=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}}],l=b.call(k),m=d.get_string("editdefaults","tool_dataprivacy",a("#defaults-header").text());a.when(l[0],l[1],m).then(function(a,b,c){var d=a.options,e=b.options;return h(c,g,i,j,null,d,e,null),!0})["catch"](c.exception)}),a(j.NEW_ACTIVITY_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}},{methodname:"tool_dataprivacy_get_activity_options",args:{nodefaults:!0}}],j=b.call(i),k=d.get_string("addnewdefaults","tool_dataprivacy");a.when(j[0],j[1],j[2],k).then(function(a,b,c,d){var e=a.options,f=b.options,i=c.options;return h(d,g,null,null,null,e,f,i),!0})["catch"](c.exception)}),a(j.EDIT_ACTIVITY_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=f.data("category"),j=f.data("purpose"),k=f.data("activityname"),l=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}},{methodname:"tool_dataprivacy_get_activity_options",args:{}}],m=b.call(l),n=d.get_string("editmoduledefaults","tool_dataprivacy");a.when(m[0],m[1],m[2],n).then(function(a,b,c,d){var e=a.options,f=b.options,l=c.options;return h(d,g,i,j,k,e,f,l),!0})["catch"](c.exception)}),a(j.DELETE_ACTIVITY_DEFAULTS).click(function(b){b.preventDefault();var h=a(this),j=h.data("contextlevel"),l=h.data("activityname"),m=h.data("activitydisplayname"),n=k,o=k;e.create({title:d.get_string("deletedefaults","tool_dataprivacy",m),body:g.render("tool_dataprivacy/delete_activity_defaults",{activityname:m}),type:e.types.SAVE_CANCEL,large:!0}).then(function(a){return a.setSaveButtonText(d.get_string("delete")),a.getRoot().on(f.save,function(){i(j,n,o,l,!1)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show(),!0})["catch"](c.exception)})},{init:function(){return new l}}});
@@ -0,0 +1,315 @@
// 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/>.
/**
* AMD module for data registry defaults actions.
*
* @module tool_dataprivacy/defaultsactions
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/notification',
'core/str',
'core/modal_factory',
'core/modal_events',
'core/templates'],
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
/**
* List of action selectors.
*
* @type {{EDIT_LEVEL_DEFAULTS: string}}
* @type {{NEW_ACTIVITY_DEFAULTS: string}}
* @type {{EDIT_ACTIVITY_DEFAULTS: string}}
* @type {{DELETE_ACTIVITY_DEFAULTS: string}}
*/
var ACTIONS = {
EDIT_LEVEL_DEFAULTS: '[data-action="edit-level-defaults"]',
NEW_ACTIVITY_DEFAULTS: '[data-action="new-activity-defaults"]',
EDIT_ACTIVITY_DEFAULTS: '[data-action="edit-activity-defaults"]',
DELETE_ACTIVITY_DEFAULTS: '[data-action="delete-activity-defaults"]'
};
/** @type {{INHERIT: Number}} **/
var INHERIT = -1;
/**
* DefaultsActions class.
*/
var DefaultsActions = function() {
this.registerEvents();
};
/**
* Register event listeners.
*/
DefaultsActions.prototype.registerEvents = function() {
$(ACTIONS.EDIT_LEVEL_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
var category = button.data('category');
var purpose = button.data('purpose');
// Get options.
var requests = [
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}}
];
var promises = Ajax.call(requests);
var titlePromise = Str.get_string('editdefaults', 'tool_dataprivacy', $('#defaults-header').text());
$.when(promises[0], promises[1], titlePromise).then(function(categoryResponse, purposeResponse, title) {
var categories = categoryResponse.options;
var purposes = purposeResponse.options;
showDefaultsFormModal(title, contextLevel, category, purpose, null, categories, purposes, null);
return true;
}).catch(Notification.exception);
});
$(ACTIONS.NEW_ACTIVITY_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
// Get options.
var requests = [
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}},
{methodname: 'tool_dataprivacy_get_activity_options', args: {'nodefaults': true}}
];
var promises = Ajax.call(requests);
var titlePromise = Str.get_string('addnewdefaults', 'tool_dataprivacy');
$.when(promises[0], promises[1], promises[2], titlePromise).then(
function(categoryResponse, purposeResponse, activityResponse, title) {
var categories = categoryResponse.options;
var purposes = purposeResponse.options;
var activities = activityResponse.options;
showDefaultsFormModal(title, contextLevel, null, null, null, categories, purposes, activities);
return true;
}).catch(Notification.exception);
}
);
$(ACTIONS.EDIT_ACTIVITY_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
var category = button.data('category');
var purpose = button.data('purpose');
var activity = button.data('activityname');
// Get options.
var requests = [
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}},
{methodname: 'tool_dataprivacy_get_activity_options', args: {}}
];
var promises = Ajax.call(requests);
var titlePromise = Str.get_string('editmoduledefaults', 'tool_dataprivacy');
$.when(promises[0], promises[1], promises[2], titlePromise).then(
function(categoryResponse, purposeResponse, activityResponse, title) {
var categories = categoryResponse.options;
var purposes = purposeResponse.options;
var activities = activityResponse.options;
showDefaultsFormModal(title, contextLevel, category, purpose, activity, categories, purposes, activities);
return true;
}).catch(Notification.exception);
}
);
$(ACTIONS.DELETE_ACTIVITY_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
var activity = button.data('activityname');
var activityDisplayName = button.data('activitydisplayname');
// Set category and purpose to inherit (-1).
var category = INHERIT;
var purpose = INHERIT;
ModalFactory.create({
title: Str.get_string('deletedefaults', 'tool_dataprivacy', activityDisplayName),
body: Templates.render('tool_dataprivacy/delete_activity_defaults', {"activityname": activityDisplayName}),
type: ModalFactory.types.SAVE_CANCEL,
large: true
}).then(function(modal) {
modal.setSaveButtonText(Str.get_string('delete'));
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
setContextDefaults(contextLevel, category, purpose, activity, false);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
modal.show();
return true;
}).catch(Notification.exception);
});
};
/**
* Prepares and renders the modal for setting the defaults for the given context level/plugin.
*
* @param {String} title The modal's title.
* @param {Number} contextLevel The context level to set defaults for.
* @param {Number} category The current category ID.
* @param {Number} purpose The current purpose ID.
* @param {String} activity The plugin name of the activity. Optional.
* @param {Array} categoryOptions The list of category options.
* @param {Array} purposeOptions The list of purpose options.
* @param {Array} activityOptions The list of activity options. Optional.
*/
function showDefaultsFormModal(title, contextLevel, category, purpose, activity,
categoryOptions, purposeOptions, activityOptions) {
if (category !== null) {
categoryOptions.forEach(function(currentValue) {
if (currentValue.id === category) {
currentValue.selected = true;
}
});
}
if (purpose !== null) {
purposeOptions.forEach(function(currentValue) {
if (currentValue.id === purpose) {
currentValue.selected = true;
}
});
}
var templateContext = {
"contextlevel": contextLevel,
"categoryoptions": categoryOptions,
"purposeoptions": purposeOptions
};
// Check the activityOptions parameter that was passed.
if (activityOptions !== null && activityOptions.length) {
// Check the activity parameter that was passed.
if (activity === null) {
// We're setting a new defaults for a module.
templateContext.newactivitydefaults = true;
} else {
// Edit mode. Set selection.
activityOptions.forEach(function(currentValue) {
if (activity === currentValue.name) {
currentValue.selected = true;
}
});
}
templateContext.modemodule = true;
templateContext.activityoptions = activityOptions;
}
ModalFactory.create({
title: title,
body: Templates.render('tool_dataprivacy/category_purpose_form', templateContext),
type: ModalFactory.types.SAVE_CANCEL,
large: true
}).then(function(modal) {
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
var activity = $('#activity');
var activityVal = typeof activity !== 'undefined' ? activity.val() : null;
var override = $('#override');
var overrideVal = typeof override !== 'undefined' ? override.is(':checked') : false;
setContextDefaults($('#contextlevel').val(), $('#category').val(), $('#purpose').val(), activityVal, overrideVal);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
modal.show();
return modal;
}).catch(Notification.exception);
}
/**
* Calls a the tool_dataprivacy_set_context_defaults WS function.
*
* @param {Number} contextLevel The context level.
* @param {Number} category The category ID.
* @param {Number} purpose The purpose ID.
* @param {String} activity The plugin name of the activity module.
* @param {Boolean} override Whether to override custom instances.
*/
function setContextDefaults(contextLevel, category, purpose, activity, override) {
var request = {
methodname: 'tool_dataprivacy_set_context_defaults',
args: {
'contextlevel': contextLevel,
'category': category,
'purpose': purpose,
'override': override,
'activity': activity
}
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
window.location.reload();
}
});
}
return /** @alias module:tool_dataprivacy/defaultsactions */ {
// Public variables and functions.
/**
* Initialise the module.
*
* @method init
* @return {DefaultsActions}
*/
'init': function() {
return new DefaultsActions();
}
};
});
+88 -1
View File
@@ -24,7 +24,7 @@
namespace tool_dataprivacy;
use coding_exception;
use context_course;
use context_helper;
use context_system;
use core\invalid_persistent_exception;
use core\message\message;
@@ -1141,4 +1141,91 @@ class api {
return $approvedcollection;
}
/**
* Updates the default category and purpose for a given context level (and optionally, a plugin).
*
* @param int $contextlevel The context level.
* @param int $categoryid The ID matching the category.
* @param int $purposeid The ID matching the purpose record.
* @param int $activity The name of the activity that we're making a defaults configuration for.
* @param bool $override Whether to override the purpose/categories of existing instances to these defaults.
* @return boolean True if set/unset config succeeds. Otherwise, it throws an exception.
*/
public static function set_context_defaults($contextlevel, $categoryid, $purposeid, $activity = null, $override = false) {
global $DB;
self::check_can_manage_data_registry();
// Get the class name associated with this context level.
$classname = context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname, $activity);
// Check the default category to be set.
if ($categoryid == context_instance::INHERIT) {
unset_config($categoryvar, 'tool_dataprivacy');
} else {
// Make sure the given category ID exists first.
$categorypersistent = new category($categoryid);
$categorypersistent->read();
// Then set the new default value.
set_config($categoryvar, $categoryid, 'tool_dataprivacy');
}
// Check the default purpose to be set.
if ($purposeid == context_instance::INHERIT) {
// If the defaults is set to inherit, just unset the config value.
unset_config($purposevar, 'tool_dataprivacy');
} else {
// Make sure the given purpose ID exists first.
$purposepersistent = new purpose($purposeid);
$purposepersistent->read();
// Then set the new default value.
set_config($purposevar, $purposeid, 'tool_dataprivacy');
}
// Unset instances that have been assigned with custom purpose and category, if override was specified.
if ($override) {
// We'd like to find context IDs that we want to unset.
$statements = ["SELECT c.id as contextid FROM {context} c"];
// Based on this context level.
$params = ['contextlevel' => $contextlevel];
if ($contextlevel == CONTEXT_MODULE) {
// If we're deleting module context instances, we need to make sure the instance ID is in the course modules table.
$statements[] = "JOIN {course_modules} cm ON cm.id = c.instanceid";
// And that the module is listed on the modules table.
$statements[] = "JOIN {modules} m ON m.id = cm.module";
if ($activity) {
// If we're overriding for an activity module, make sure that the context instance matches that activity.
$statements[] = "AND m.name = :modname";
$params['modname'] = $activity;
}
}
// Make sure this context instance exists in the tool_dataprivacy_ctxinstance table.
$statements[] = "JOIN {tool_dataprivacy_ctxinstance} tdc ON tdc.contextid = c.id";
// And that the context level of this instance matches the given context level.
$statements[] = "WHERE c.contextlevel = :contextlevel";
// Build our SQL query by gluing the statements.
$sql = implode("\n", $statements);
// Get the context records matching our query.
$contextids = $DB->get_fieldset_sql($sql, $params);
// Delete the matching context instances.
foreach ($contextids as $contextid) {
if ($instance = context_instance::get_record_by_contextid($contextid, false)) {
self::unset_context_instance($instance);
}
}
}
return true;
}
}
@@ -28,10 +28,7 @@
namespace tool_dataprivacy;
use coding_exception;
use tool_dataprivacy\purpose;
use tool_dataprivacy\category;
use tool_dataprivacy\contextlevel;
use tool_dataprivacy\context_instance;
use core\persistent;
defined('MOODLE_INTERNAL') || die();
@@ -57,13 +54,22 @@ class data_registry {
/**
* Returns purpose and category var names from a context class name
*
* @param string $classname
* @param string $classname The context level's class.
* @param string $pluginname The name of the plugin associated with the context level.
* @return string[]
*/
public static function var_names_from_context($classname) {
public static function var_names_from_context($classname, $pluginname = '') {
$pluginname = trim($pluginname);
if (!empty($pluginname)) {
$categoryvar = $classname . '_' . $pluginname . '_category';
$purposevar = $classname . '_' . $pluginname . '_purpose';
} else {
$categoryvar = $classname . '_category';
$purposevar = $classname . '_purpose';
}
return [
$classname . '_purpose',
$classname . '_category',
$purposevar,
$categoryvar
];
}
@@ -72,17 +78,31 @@ class data_registry {
*
* The caller code is responsible of checking that $contextlevel is an integer.
*
* @param int $contextlevel
* @return int|false[]
* @param int $contextlevel The context level.
* @param string $pluginname The name of the plugin associated with the context level.
* @return int[]|false[]
*/
public static function get_defaults($contextlevel) {
public static function get_defaults($contextlevel, $pluginname = '') {
$classname = \context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = self::var_names_from_context($classname);
list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
$purposeid = get_config('tool_dataprivacy', $purposevar);
$categoryid = get_config('tool_dataprivacy', $categoryvar);
if (!empty($pluginname)) {
list($purposevar, $categoryvar) = self::var_names_from_context($classname);
// If the plugin-level doesn't have a default purpose set, try the context level.
if ($purposeid === false) {
$purposeid = get_config('tool_dataprivacy', $purposevar);
}
// If the plugin-level doesn't have a default category set, try the context level.
if ($categoryid === false) {
$categoryid = get_config('tool_dataprivacy', $categoryvar);
}
}
if (empty($purposeid)) {
$purposeid = false;
}
@@ -197,9 +217,15 @@ class data_registry {
// Use the context level value as we don't allow people to set specific instances values.
return self::get_effective_contextlevel_value($context->contextlevel, $element);
} else {
// Check if we need to pass the plugin name of an activity.
$forplugin = '';
if ($context->contextlevel == CONTEXT_MODULE) {
list($course, $cm) = get_course_and_cm_from_cmid($context->instanceid);
$forplugin = $cm->modname;
}
// Use the default context level value.
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
$context->contextlevel
$context->contextlevel, false, false, $forplugin
);
return self::get_element_instance($element, $$fieldname);
}
@@ -283,14 +309,15 @@ class data_registry {
* Returns the effective default purpose and category for a context level.
*
* @param int $contextlevel
* @param int $forcedpurposevalue Use this value as if this was this context level purpose.
* @param int $forcedcategoryvalue Use this value as if this was this context level category.
* @param int|bool $forcedpurposevalue Use this value as if this was this context level purpose.
* @param int|bool $forcedcategoryvalue Use this value as if this was this context level category.
* @param string $activity The plugin name of the activity.
* @return int[]
*/
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
$forcedcategoryvalue = false) {
$forcedcategoryvalue = false, $activity = '') {
list($purposeid, $categoryid) = self::get_defaults($contextlevel);
list($purposeid, $categoryid) = self::get_defaults($contextlevel, $activity);
// Honour forced values.
if ($forcedpurposevalue) {
+281
View File
@@ -1110,6 +1110,287 @@ class external extends external_api {
]);
}
/**
* Parameters for set_context_defaults().
*
* @return external_function_parameters
*/
public static function set_context_defaults_parameters() {
return new external_function_parameters([
'contextlevel' => new external_value(PARAM_INT, 'The context level', VALUE_REQUIRED),
'category' => new external_value(PARAM_INT, 'The default category for the given context level', VALUE_REQUIRED),
'purpose' => new external_value(PARAM_INT, 'The default purpose for the given context level', VALUE_REQUIRED),
'activity' => new external_value(PARAM_PLUGIN, 'The plugin name of the activity', VALUE_DEFAULT, null),
'override' => new external_value(PARAM_BOOL, 'Whether to override existing instances with the defaults', VALUE_DEFAULT,
false),
]);
}
/**
* Updates the default category and purpose for a given context level (and optionally, a plugin).
*
* @param int $contextlevel The context level.
* @param int $category The ID matching the category.
* @param int $purpose The ID matching the purpose record.
* @param int $activity The name of the activity that we're making a defaults configuration for.
* @param bool $override Whether to override the purpose/categories of existing instances to these defaults.
* @return array
*/
public static function set_context_defaults($contextlevel, $category, $purpose, $activity, $override) {
$warnings = [];
$params = external_api::validate_parameters(self::set_context_defaults_parameters(), [
'contextlevel' => $contextlevel,
'category' => $category,
'purpose' => $purpose,
'activity' => $activity,
'override' => $override,
]);
$contextlevel = $params['contextlevel'];
$category = $params['category'];
$purpose = $params['purpose'];
$activity = $params['activity'];
$override = $params['override'];
// Validate context.
$context = context_system::instance();
self::validate_context($context);
// Set the context defaults.
$result = api::set_context_defaults($contextlevel, $category, $purpose, $activity, $override);
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* Returns for set_context_defaults().
*
* @return external_single_structure
*/
public static function set_context_defaults_returns() {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'Whether the context defaults were successfully set or not'),
'warnings' => new external_warnings()
]);
}
/**
* Parameters for get_category_options().
*
* @return external_function_parameters
*/
public static function get_category_options_parameters() {
return new external_function_parameters([
'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
]);
}
/**
* Fetches a list of data category options containing category IDs as keys and the category name for the value.
*
* @param bool $includeinherit Whether to include the "Inherit" option.
* @param bool $includenotset Whether to include the "Not set" option.
* @return array
*/
public static function get_category_options($includeinherit, $includenotset) {
$warnings = [];
$params = self::validate_parameters(self::get_category_options_parameters(), [
'includeinherit' => $includeinherit,
'includenotset' => $includenotset
]);
$includeinherit = $params['includeinherit'];
$includenotset = $params['includenotset'];
$context = context_system::instance();
self::validate_context($context);
$categories = api::get_categories();
$options = data_registry_page::category_options($categories, $includenotset, $includeinherit);
$categoryoptions = [];
foreach ($options as $id => $name) {
$categoryoptions[] = [
'id' => $id,
'name' => $name,
];
}
return [
'options' => $categoryoptions,
'warnings' => $warnings
];
}
/**
* Returns for get_category_options().
*
* @return external_single_structure
*/
public static function get_category_options_returns() {
$optiondefinition = new external_single_structure(
[
'id' => new external_value(PARAM_INT, 'The category ID'),
'name' => new external_value(PARAM_TEXT, 'The category name'),
]
);
return new external_single_structure([
'options' => new external_multiple_structure($optiondefinition),
'warnings' => new external_warnings()
]);
}
/**
* Parameters for get_purpose_options().
*
* @return external_function_parameters
*/
public static function get_purpose_options_parameters() {
return new external_function_parameters([
'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
]);
}
/**
* Fetches a list of data storage purposes containing purpose IDs as keys and the purpose name for the value.
*
* @param bool $includeinherit Whether to include the "Inherit" option.
* @param bool $includenotset Whether to include the "Not set" option.
* @return array
*/
public static function get_purpose_options($includeinherit, $includenotset) {
$warnings = [];
$params = self::validate_parameters(self::get_category_options_parameters(), [
'includeinherit' => $includeinherit,
'includenotset' => $includenotset
]);
$includeinherit = $params['includeinherit'];
$includenotset = $params['includenotset'];
$context = context_system::instance();
self::validate_context($context);
$purposes = api::get_purposes();
$options = data_registry_page::purpose_options($purposes, $includenotset, $includeinherit);
$purposeoptions = [];
foreach ($options as $id => $name) {
$purposeoptions[] = [
'id' => $id,
'name' => $name,
];
}
return [
'options' => $purposeoptions,
'warnings' => $warnings
];
}
/**
* Returns for get_purpose_options().
*
* @return external_single_structure
*/
public static function get_purpose_options_returns() {
$optiondefinition = new external_single_structure(
[
'id' => new external_value(PARAM_INT, 'The purpose ID'),
'name' => new external_value(PARAM_TEXT, 'The purpose name'),
]
);
return new external_single_structure([
'options' => new external_multiple_structure($optiondefinition),
'warnings' => new external_warnings()
]);
}
/**
* Parameters for get_activity_options().
*
* @return external_function_parameters
*/
public static function get_activity_options_parameters() {
return new external_function_parameters([
'nodefaults' => new external_value(PARAM_BOOL, 'Whether to fetch all activities or only those without defaults',
VALUE_DEFAULT, false),
]);
}
/**
* Fetches a list of activity options for setting data registry defaults.
*
* @param boolean $nodefaults If false, it will fetch all of the activities. Otherwise, it will only fetch the activities
* that don't have defaults yet (e.g. when adding a new activity module defaults).
* @return array
*/
public static function get_activity_options($nodefaults) {
$warnings = [];
$params = self::validate_parameters(self::get_activity_options_parameters(), [
'nodefaults' => $nodefaults,
]);
$nodefaults = $params['nodefaults'];
$context = context_system::instance();
self::validate_context($context);
// Get activity module plugin info.
$pluginmanager = \core_plugin_manager::instance();
$modplugins = $pluginmanager->get_enabled_plugins('mod');
$modoptions = [];
// Get the module-level defaults. data_registry::get_defaults falls back to this when there are no activity defaults.
list($levelpurpose, $levelcategory) = data_registry::get_defaults(CONTEXT_MODULE);
foreach ($modplugins as $name) {
// Check if we have default purpose and category for this module if we want don't want to fetch everything.
if ($nodefaults) {
list($purpose, $category) = data_registry::get_defaults(CONTEXT_MODULE, $name);
// Compare this with the module-level defaults.
if ($purpose !== $levelpurpose || $category !== $levelcategory) {
// If the defaults for this activity has been already set, there's no need to add this in the list of options.
continue;
}
}
$displayname = $pluginmanager->plugin_name('mod_' . $name);
$modoptions[] = (object)[
'name' => $name,
'displayname' => $displayname
];
}
return [
'options' => $modoptions,
'warnings' => $warnings
];
}
/**
* Returns for get_category_options().
*
* @return external_single_structure
*/
public static function get_activity_options_returns() {
$optionsdefinition = new external_single_structure(
[
'name' => new external_value(PARAM_TEXT, 'The plugin name of the activity'),
'displayname' => new external_value(PARAM_TEXT, 'The display name of the activity'),
]
);
return new external_single_structure([
'options' => new external_multiple_structure($optionsdefinition),
'warnings' => new external_warnings()
]);
}
/**
* Gets the structure of a tree node (link + child branches).
*
@@ -25,6 +25,8 @@ namespace tool_dataprivacy\external;
defined('MOODLE_INTERNAL') || die();
use core\external\persistent_exporter;
use tool_dataprivacy\category;
use tool_dataprivacy\context_instance;
/**
* Class for exporting field data.
@@ -53,4 +55,25 @@ class category_exporter extends persistent_exporter {
'context' => 'context',
);
}
/**
* Utility function that fetches a category name from the given ID.
*
* @param int $categoryid The category ID. Could be INHERIT (false, -1), NOT_SET (0), or the actual ID.
* @return string The purpose name.
*/
public static function get_name($categoryid) {
global $PAGE;
if ($categoryid === false || $categoryid == context_instance::INHERIT) {
return get_string('inherit', 'tool_dataprivacy');
} else if ($categoryid == context_instance::NOTSET) {
return get_string('notset', 'tool_dataprivacy');
} else {
$purpose = new category($categoryid);
$output = $PAGE->get_renderer('tool_dataprivacy');
$exporter = new self($purpose, ['context' => \context_system::instance()]);
$data = $exporter->export($output);
return $data->name;
}
}
}
@@ -29,6 +29,7 @@ use core\external\persistent_exporter;
use DateInterval;
use Exception;
use renderer_base;
use tool_dataprivacy\context_instance;
use tool_dataprivacy\purpose;
/**
@@ -143,4 +144,25 @@ class purpose_exporter extends persistent_exporter {
return $values;
}
/**
* Utility function that fetches a purpose name from the given ID.
*
* @param int $purposeid The purpose ID. Could be INHERIT (false, -1), NOT_SET (0), or the actual ID.
* @return string The purpose name.
*/
public static function get_name($purposeid) {
global $PAGE;
if ($purposeid === false || $purposeid == context_instance::INHERIT) {
return get_string('inherit', 'tool_dataprivacy');
} else if ($purposeid == context_instance::NOTSET) {
return get_string('notset', 'tool_dataprivacy');
} else {
$purpose = new purpose($purposeid);
$output = $PAGE->get_renderer('tool_dataprivacy');
$exporter = new self($purpose, ['context' => \context_system::instance()]);
$data = $exporter->export($output);
return $data->name;
}
}
}
@@ -1,81 +0,0 @@
<?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/>.
/**
* This file contains the defaults form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\form;
defined('MOODLE_INTERNAL') || die();
use \tool_dataprivacy\output\data_registry_page;
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Context levels defaults form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class defaults extends \moodleform {
/**
* Define the form.
*/
public function definition() {
global $OUTPUT;
$mform = $this->_form;
$mform->setDisableShortforms();
$notification = $OUTPUT->notification(get_string('defaultsinfo', 'tool_dataprivacy'),
\core\output\notification::NOTIFY_INFO);
$mform->addElement('html', $notification);
foreach ($this->_customdata['levels'] as $level => $classname) {
$mform->addElement('header', $classname . '-header',
get_string('contextlevelname' . $level, 'tool_dataprivacy'));
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$includeinherit = true;
if ($level == CONTEXT_SYSTEM) {
$includeinherit = false;
}
$categoryoptions = data_registry_page::category_options($this->_customdata['categories'], false, $includeinherit);
$purposeoptions = data_registry_page::category_options($this->_customdata['purposes'], false, $includeinherit);
$mform->addElement('select', $categoryvar, get_string('category', 'tool_dataprivacy'), $categoryoptions);
$mform->addHelpButton($categoryvar, 'categorydefault', 'tool_dataprivacy');
$mform->setType($categoryvar, PARAM_INT);
$mform->addElement('select', $purposevar, get_string('purpose', 'tool_dataprivacy'), $purposeoptions);
$mform->addHelpButton($purposevar, 'purposedefault', 'tool_dataprivacy');
$mform->setType($purposevar, PARAM_INT);
}
$this->add_action_buttons(true, get_string('savechanges'));
}
}
@@ -425,7 +425,7 @@ class data_registry_page implements renderable, templatable {
/**
* From a list of purpose persistents to a list of id => name purposes.
*
* @param \tool_dataprivacy\purpose $purposes
* @param \tool_dataprivacy\purpose[] $purposes
* @param bool $includenotset
* @param bool $includeinherit
* @return string[]
@@ -442,7 +442,7 @@ class data_registry_page implements renderable, templatable {
/**
* From a list of category persistents to a list of id => name categories.
*
* @param \tool_dataprivacy\category $categories
* @param \tool_dataprivacy\category[] $categories
* @param bool $includenotset
* @param bool $includeinherit
* @return string[]
@@ -0,0 +1,178 @@
<?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 data for the data registry defaults.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use action_menu_link_primary;
use coding_exception;
use moodle_exception;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\external\category_exporter;
use tool_dataprivacy\external\purpose_exporter;
/**
* Class containing data for the data registry defaults.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class defaults_page implements renderable, templatable {
/** @var int $mode The display mode. */
protected $mode = null;
/** @var int $category The default category for the given mode. */
protected $category = null;
/** @var int $purpose The default purpose for the given mode. */
protected $purpose = null;
/** @var stdClass[] $otherdefaults Other defaults for the given mode. */
protected $otherdefaults = [];
/** @var bool $canedit Whether editing is allowed. */
protected $canedit = false;
/**
* Construct this renderable.
*
* @param int $mode The display mode.
* @param int $category The default category for the given mode.
* @param int $purpose The default purpose for the given mode.
* @param stdClass[] $otherdefaults Other defaults for the given mode.
* @param bool $canedit Whether editing is allowed.
*/
public function __construct($mode, $category, $purpose, $otherdefaults = [], $canedit = false) {
$this->mode = $mode;
$this->category = $category;
$this->purpose = $purpose;
$this->otherdefaults = $otherdefaults;
$this->canedit = $canedit;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
* @throws coding_exception
* @throws moodle_exception
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
// Set tab URLs.
$coursecaturl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_COURSECAT]);
$courseurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_COURSE]);
$moduleurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_MODULE]);
$blockurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_BLOCK]);
$data->coursecaturl = $coursecaturl;
$data->courseurl = $courseurl;
$data->moduleurl = $moduleurl;
$data->blockurl = $blockurl;
// Set display mode.
switch ($this->mode) {
case CONTEXT_COURSECAT:
$data->modecoursecat = true;
break;
case CONTEXT_COURSE:
$data->modecourse = true;
break;
case CONTEXT_MODULE:
$data->modemodule = true;
break;
case CONTEXT_BLOCK:
$data->modeblock = true;
break;
default:
$data->modecoursecat = true;
break;
}
// Set config variables.
$configname = \context_helper::get_class_for_level($this->mode);
list($purposevar, $categoryvar) = data_registry::var_names_from_context($configname);
$data->categoryvar = $categoryvar;
$data->purposevar = $purposevar;
// Set default category.
$data->categoryid = $this->category;
$data->category = category_exporter::get_name($this->category);
// Set default purpose.
$data->purposeid = $this->purpose;
$data->purpose = purpose_exporter::get_name($this->purpose);
// Set other defaults.
$otherdefaults = [];
$url = new moodle_url('#');
foreach ($this->otherdefaults as $pluginname => $values) {
$defaults = [
'name' => $values->name,
'category' => category_exporter::get_name($values->category),
'purpose' => purpose_exporter::get_name($values->purpose),
];
if ($this->canedit) {
$actions = [];
// Edit link.
$editattrs = [
'data-action' => 'edit-activity-defaults',
'data-contextlevel' => $this->mode,
'data-activityname' => $pluginname,
'data-category' => $values->category,
'data-purpose' => $values->purpose,
];
$editlink = new action_menu_link_primary($url, new \pix_icon('t/edit', get_string('edit')),
get_string('edit'), $editattrs);
$actions[] = $editlink->export_for_template($output);
// Delete link.
$deleteattrs = [
'data-action' => 'delete-activity-defaults',
'data-contextlevel' => $this->mode,
'data-activityname' => $pluginname,
'data-activitydisplayname' => $values->name,
];
$deletelink = new action_menu_link_primary($url, new \pix_icon('t/delete', get_string('delete')),
get_string('delete'), $deleteattrs);
$actions[] = $deletelink->export_for_template($output);
$defaults['actions'] = $actions;
}
$otherdefaults[] = (object)$defaults;
}
$data->otherdefaults = $otherdefaults;
$data->canedit = $this->canedit;
$data->contextlevel = $this->mode;
return $data;
}
}
+40
View File
@@ -173,4 +173,44 @@ $functions = [
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_set_context_defaults' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'set_context_defaults',
'classpath' => '',
'description' => 'Updates the default category and purpose for a given context level (and optionally, a plugin)',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_category_options' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_category_options',
'classpath' => '',
'description' => 'Fetches a list of data category options',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_purpose_options' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_purpose_options',
'classpath' => '',
'description' => 'Fetches a list of data storage purpose options',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_activity_options' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_activity_options',
'classpath' => '',
'description' => 'Fetches a list of activity options',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
];
+27 -35
View File
@@ -32,47 +32,39 @@ $title = get_string('setdefaults', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
$levels = \context_helper::get_all_levels();
// They are set through the context level site and user.
unset($levels[CONTEXT_SYSTEM]);
unset($levels[CONTEXT_USER]);
$mode = optional_param('mode', CONTEXT_COURSECAT, PARAM_INT);
$classname = context_helper::get_class_for_level($mode);
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$purpose = get_config('tool_dataprivacy', $purposevar);
$category = get_config('tool_dataprivacy', $categoryvar);
$customdata = [
'levels' => $levels,
'purposes' => \tool_dataprivacy\api::get_purposes(),
'categories' => \tool_dataprivacy\api::get_categories(),
];
$form = new \tool_dataprivacy\form\defaults($PAGE->url->out(false), $customdata);
$otherdefaults = [];
if ($mode == CONTEXT_MODULE) {
// Get activity module plugin info.
$pluginmanager = core_plugin_manager::instance();
$modplugins = $pluginmanager->get_enabled_plugins('mod');
$toform = new stdClass();
foreach ($levels as $level => $classname) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$toform->{$purposevar} = get_config('tool_dataprivacy', $purposevar);
$toform->{$categoryvar} = get_config('tool_dataprivacy', $categoryvar);
}
$form->set_data($toform);
$returnurl = new \moodle_url('/admin/tool/dataprivacy/dataregistry.php');
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
foreach ($levels as $level => $classname) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
if (isset($data->{$purposevar})) {
set_config($purposevar, $data->{$purposevar}, 'tool_dataprivacy');
}
if (isset($data->{$categoryvar})) {
set_config($categoryvar, $data->{$categoryvar}, 'tool_dataprivacy');
foreach ($modplugins as $name) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname, $name);
$plugincategory = get_config('tool_dataprivacy', $categoryvar);
$pluginpurpose = get_config('tool_dataprivacy', $purposevar);
if ($plugincategory === false && $pluginpurpose === false) {
// If no purpose and category has been set for this plugin, then there's no need to show this on the list.
continue;
}
$displayname = $pluginmanager->plugin_name('mod_' . $name);
$otherdefaults[$name] = (object)[
'name' => $displayname,
'category' => $plugincategory,
'purpose' => $pluginpurpose,
];
}
redirect($returnurl, get_string('defaultssaved', 'tool_dataprivacy'),
0, \core\output\notification::NOTIFY_SUCCESS);
}
$defaultspage = new \tool_dataprivacy\output\defaults_page($mode, $category, $purpose, $otherdefaults, true);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
$form->display();
echo $output->render_from_template('tool_dataprivacy/defaults_page', $defaultspage->export_for_template($output));
echo $output->footer();
@@ -28,6 +28,7 @@ $string['pluginname'] = 'Data privacy';
$string['pluginname_help'] = 'Data privacy plugin';
$string['activitiesandresources'] = 'Activities and resources';
$string['addcategory'] = 'Add category';
$string['addnewdefaults'] = 'Add a new module default';
$string['addpurpose'] = 'Add purpose';
$string['approve'] = 'Approve';
$string['approverequest'] = 'Approve request';
@@ -76,9 +77,12 @@ $string['datarequests'] = 'Data requests';
$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
$string['daterequested'] = 'Date requested';
$string['daterequesteddetail'] = 'Date requested:';
$string['defaultsinfo'] = 'Default categories and purposes are applied to all newly created instances.';
$string['defaultsinfo'] = 'Default categories and purposes are applied to all new and existing instances where a value is not set.';
$string['defaultswarninginfo'] = 'Warning: Changing these defaults may affect the retention period of existing instances.';
$string['deletecategory'] = 'Delete category';
$string['deletecategorytext'] = 'Are you sure you want to delete the category \'{$a}\'?';
$string['deletedefaults'] = 'Delete defaults: {$a}';
$string['deletedefaultsconfirmation'] = 'Are you sure you want to delete the default category and purpose for {$a} modules?';
$string['deleteexpiredcontextstask'] = 'Delete expired contexts';
$string['deleteexpireddatarequeststask'] = 'Delete files from completed data requests that have expired';
$string['deletepurpose'] = 'Delete purpose';
@@ -95,6 +99,8 @@ $string['dporolemapping_desc'] = 'The privacy officer can manage data requests.
$string['editcategories'] = 'Edit categories';
$string['editcategory'] = 'Edit category';
$string['editcategories'] = 'Edit categories';
$string['editdefaults'] = 'Edit defaults: {$a}';
$string['editmoduledefaults'] = 'Edit module defaults';
$string['editpurpose'] = 'Edit purpose';
$string['editpurposes'] = 'Edit purposes';
$string['effectiveretentionperiodcourse'] = '{$a} (after the course end date)';
@@ -185,6 +191,7 @@ $string['nopurposes'] = 'There are no purposes yet';
$string['nosubjectaccessrequests'] = 'There are no data requests that you need to act on';
$string['nosystemdefaults'] = 'Site purpose and category have not yet been defined.';
$string['notset'] = 'Not set (use the default value)';
$string['overrideinstances'] = 'Reset instances with custom values';
$string['pluginregistry'] = 'Plugin privacy registry';
$string['pluginregistrytitle'] = 'Plugin privacy compliance registry';
$string['privacy'] = 'Privacy';
+4
View File
@@ -28,3 +28,7 @@ dd a.contactdpo {
[data-region="data-requests-table"] .moodle-actionmenu {
min-width: 150px;
}
.context-level-view {
margin: 1em;
}
@@ -0,0 +1,99 @@
{{!
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/category_purpose_form
Manage data registry defaults.
Classes required for JS:
Data attributes required for JS:
Context variables required for this template:
* actionurl String The action URL.
* contextlevel Number - The context level.
* modemodule Boolean - Whether to display the activity select element.
* activityoptions Array - An array of objects for the activity select element.
* categoryoptions Array - An array of objects for the category select element.
* purposeoptions Array - An array of objects for the purpose select element.
Example context (json):
{
"actionurl": "#",
"contextlevel": 70,
"newactivitydefaults": true,
"modemodule": true,
"activityoptions": [
{ "name": "assign", "displayname": "Assignment" },
{ "name": "forum", "displayname": "Forum", "selected": true },
{ "name": "lesson", "displayname": "Lesson" },
{ "name": "quiz", "displayname": "Quiz" }
],
"categoryoptions": [
{ "id": 1, "name": "Category 1" },
{ "id": 2, "name": "Category 2", "selected": true },
{ "id": 3, "name": "Category 3" }
],
"purposeoptions": [
{ "id": 1, "name": "Purpose 1" },
{ "id": 2, "name": "Purpose 2" },
{ "id": 3, "name": "Purpose 3", "selected": true }
]
}
}}
<div class="alert alert-warning" role="alert">
{{#str}}defaultswarninginfo, tool_dataprivacy{{/str}}
</div>
<form method="post" action="{{actionurl}}" id="category_purpose_form">
<input type="hidden" value="{{contextlevel}}" id="contextlevel" />
{{#modemodule}}
<div class="form-group">
<label for="activity">{{#str}}activitymodule{{/str}}</label>
{{^newactivitydefaults}}
<input type="hidden" id="activity" value="{{#activityoptions}}{{#selected}}{{name}}{{/selected}}{{/activityoptions}}" />
{{/newactivitydefaults}}
<select class="form-control" {{#newactivitydefaults}}id="activity" {{/newactivitydefaults}}{{^newactivitydefaults}}disabled{{/newactivitydefaults}}>
{{#activityoptions}}
<option value="{{name}}" {{#selected}}selected{{/selected}}>{{displayname}}</option>
{{/activityoptions}}
</select>
</div>
{{/modemodule}}
<div class="form-group">
<label for="category">{{#str}}category, tool_dataprivacy{{/str}}</label>
<select class="form-control" id="category">
{{#categoryoptions}}
<option value="{{id}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/categoryoptions}}
</select>
</div>
<div class="form-group">
<label for="purpose">{{#str}}purpose, tool_dataprivacy{{/str}}</label>
<select class="form-control" id="purpose">
{{#purposeoptions}}
<option value="{{id}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/purposeoptions}}
</select>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" id="override">
<label class="form-check-label" for="override">
{{#str}}overrideinstances, tool_dataprivacy{{/str}}
</label>
</div>
</form>
@@ -0,0 +1,74 @@
{{!
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/defaults_display
Renders the current default category and purpose.
Classes required for JS:
Data attributes required for JS:
Context variables required for this template:
* contextlevel Number - The context level.
* categoryid Number - The category ID.
* purposeid Number - The purpose ID.
* category String - The category name.
* purpose String - The purpose name.
* canedit Boolean - Whether this is being rendered for editing purposes.
Example context (json):
{
"category": "Awesome default category",
"categoryid": 1,
"purpose": "Awesome default purpose",
"purposeid": 2,
"canedit": true,
"contextlevel": 70
}
}}
<div class="row-fluid rtl-compatible m-t-1 m-b-1">
<div class="col-md-9 span9">
<div class="row-fluid rtl-compatible m-t-1 m-b-1">
<div class="col-md-3 span3">
<strong>{{#str}}category, tool_dataprivacy{{/str}}</strong>
</div>
<div class="col-md-9 span9">
{{category}}
</div>
</div>
<div class="row-fluid rtl-compatible m-t-1 m-b-1">
<div class="col-md-3 span3">
<strong>{{#str}}purpose, tool_dataprivacy{{/str}}</strong>
</div>
<div class="col-md-9 span9">
{{purpose}}
</div>
</div>
</div>
<div class="col-md-3 span3 align-self-center">
{{#canedit}}
<button class="btn btn-default" {{!
}}data-action="edit-level-defaults" {{!
}}data-contextlevel="{{contextlevel}}" {{!
}}data-category="{{categoryid}}" {{!
}}data-purpose="{{purposeid}}">
{{#str}}edit{{/str}}
</button>
{{/canedit}}
</div>
</div>
@@ -0,0 +1,147 @@
{{!
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/defaults_page
Manage data registry defaults.
Classes required for JS:
Data attributes required for JS:
Context variables required for this template:
* contextlevel Number - The context level.
* modecoursecat Boolean - Whether we're displaying defaults for course categories.
* modecourse Boolean - Whether we're displaying defaults for courses.
* modemodule Boolean - Whether we're displaying defaults for activity modules.
* modeblock Boolean - Whether we're displaying defaults for blocks.
* coursecaturl String - The URL for the course category defaults tab.
* courseurl String - The URL for the course defaults tab.
* moduleurl String - The URL for the activity module defaults tab.
* blockurl String - The URL for the block defaults tab.
* purposeid Number - The purpose ID for this context level.
* canedit Boolean - Whether this is being rendered for editing purposes.
* categoryid Number - The ID of the default category for this context level.
* purposeid Number - The ID of the default purpose for this context level.
* category String - The category name.
* purpose String - The purpose name.
* otherdefaults Array - An array containing the defaults for the activity modules.
Example context (json):
{
"contextlevel": 70,
"modecoursecat": false,
"modecourse": false,
"modemodule": true,
"modeblock": false,
"coursecaturl": "#",
"courseurl": "#",
"moduleurl": "#",
"blockurl": "#",
"category": "Awesome default category",
"purpose": "Awesome default purpose",
"canedit": true,
"otherdefaults": [
{
"name": "Assignment",
"category": "Category for activity modules",
"purpose": "Assessments"
},
{
"name": "Forum",
"category": "Category for activity modules",
"purpose": "Social interactions"
}
]
}
}}
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link {{#modecoursecat}}active{{/modecoursecat}}" href="{{coursecaturl}}">{{#str}}categories{{/str}}</a>
</li>
<li class="nav-item">
<a class="nav-link {{#modecourse}}active{{/modecourse}}" href="{{courseurl}}">{{#str}}courses{{/str}}</a>
</li>
<li class="nav-item">
<a class="nav-link {{#modemodule}}active{{/modemodule}}" href="{{moduleurl}}">{{#str}}activitymodules{{/str}}</a>
</li>
<li class="nav-item">
<a class="nav-link {{#modeblock}}active{{/modeblock}}" href="{{blockurl}}">{{#str}}blocks{{/str}}</a>
</li>
</ul>
</div>
<div class="card-body context-level-view">
<div class="alert alert-primary" role="alert">
{{#str}}defaultsinfo, tool_dataprivacy{{/str}}
</div>
<h4 class="card-title" id="defaults-header">
{{#modecoursecat}}{{#str}}categories{{/str}}{{/modecoursecat}}
{{#modecourse}}{{#str}}courses{{/str}}{{/modecourse}}
{{#modemodule}}{{#str}}activitymodules{{/str}}{{/modemodule}}
{{#modeblock}}{{#str}}blocks{{/str}}{{/modeblock}}
</h4>
<div>
{{> tool_dataprivacy/defaults_display}}
{{#canedit}}
{{#modemodule}}
<button class="btn btn-primary" data-action="new-activity-defaults" data-contextlevel="{{contextlevel}}">
{{#str}}addnewdefaults, tool_dataprivacy{{/str}}
</button>
{{/modemodule}}
{{/canedit}}
{{#modemodule}}
<table class="m-t-1 table table-striped">
<thead>
<tr>
<th>&nbsp;</th>
<th scope="col">{{#str}}category, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}purpose, tool_dataprivacy{{/str}}</th>
{{#canedit}}
<th scope="col">{{#str}}actions{{/str}}</th>
{{/canedit}}
</tr>
</thead>
<tbody>
{{#otherdefaults}}
<tr>
<th scope="row">{{name}}</th>
<td>{{category}}</td>
<td>{{purpose}}</td>
{{#canedit}}
<td>
{{#actions}}
{{> core/action_menu_link}}
{{/actions}}
</td>
{{/canedit}}
</tr>
{{/otherdefaults}}
</tbody>
</table>
{{/modemodule}}
</div>
</div>
</div>
{{#js}}
// Initialise the JS.
require(['tool_dataprivacy/defaultsactions'], function(ActionsMod) {
ActionsMod.init();
});
{{/js}}
@@ -0,0 +1,41 @@
{{!
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/delete_activity_defaults
Renders the confirmation.
Classes required for JS:
Data attributes required for JS:
* none
Context variables required for this template:
* activityname String - The activity name.
Example context (json):
{
"activityname": "Assignment"
}
}}
<div class="alert alert-warning" role="alert">
{{#str}}defaultswarninginfo, tool_dataprivacy{{/str}}
</div>
<div>
{{#str}}deletedefaultsconfirmation, tool_dataprivacy, {{activityname}}{{/str}}
</div>
+158
View File
@@ -1321,6 +1321,164 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
}
/**
* Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
*/
public function set_context_defaults_provider() {
$contextlevels = [
[CONTEXT_COURSECAT],
[CONTEXT_COURSE],
[CONTEXT_MODULE],
[CONTEXT_BLOCK],
];
$paramsets = [
[true, true, false, false], // Inherit category and purpose, Not for activity, Don't override.
[true, false, false, false], // Inherit category but not purpose, Not for activity, Don't override.
[false, true, false, false], // Inherit purpose but not category, Not for activity, Don't override.
[false, false, false, false], // Don't inherit both category and purpose, Not for activity, Don't override.
[false, false, false, true], // Don't inherit both category and purpose, Not for activity, Override instances.
];
$data = [];
foreach ($contextlevels as $level) {
foreach ($paramsets as $set) {
$data[] = array_merge($level, $set);
}
if ($level == CONTEXT_MODULE) {
// Add a combination where defaults for activity is being set.
$data[] = [CONTEXT_MODULE, false, false, true, false];
$data[] = [CONTEXT_MODULE, false, false, true, true];
}
}
return $data;
}
/**
* Test for \tool_dataprivacy\api::set_context_defaults()
*
* @dataProvider set_context_defaults_provider
* @param int $contextlevel The context level
* @param bool $inheritcategory Whether to set category value as INHERIT.
* @param bool $inheritpurpose Whether to set purpose value as INHERIT.
* @param bool $foractivity Whether to set defaults for an activity.
* @param bool $override Whether to override instances.
*/
public function test_set_context_defaults($contextlevel, $inheritcategory, $inheritpurpose, $foractivity, $override) {
$this->setAdminUser();
$generator = $this->getDataGenerator();
// Generate course cat, course, block, assignment, forum instances.
$coursecat = $generator->create_category();
$course = $generator->create_course(['category' => $coursecat->id]);
$block = $generator->create_block('online_users');
$assign = $generator->create_module('assign', ['course' => $course->id]);
$forum = $generator->create_module('forum', ['course' => $course->id]);
$coursecatcontext = context_coursecat::instance($coursecat->id);
$coursecontext = context_course::instance($course->id);
$blockcontext = context_block::instance($block->id);
list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
list($course, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
$assigncontext = context_module::instance($assigncm->id);
$forumcontext = context_module::instance($forumcm->id);
// Generate purposes and categories.
$category1 = api::create_category((object)['name' => 'Test category 1']);
$category2 = api::create_category((object)['name' => 'Test category 2']);
$purpose1 = api::create_purpose((object)[
'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
]);
$purpose2 = api::create_purpose((object)[
'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
]);
// Assign purposes and categories to contexts.
$coursecatctxinstance = api::set_context_instance((object) [
'contextid' => $coursecatcontext->id,
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
$coursectxinstance = api::set_context_instance((object) [
'contextid' => $coursecontext->id,
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
$blockctxinstance = api::set_context_instance((object) [
'contextid' => $blockcontext->id,
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
$assignctxinstance = api::set_context_instance((object) [
'contextid' => $assigncontext->id,
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
$forumctxinstance = api::set_context_instance((object) [
'contextid' => $forumcontext->id,
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
$categoryid = $inheritcategory ? context_instance::INHERIT : $category2->get('id');
$purposeid = $inheritpurpose ? context_instance::INHERIT : $purpose2->get('id');
$activity = '';
if ($contextlevel == CONTEXT_MODULE && $foractivity) {
$activity = 'assign';
}
$result = api::set_context_defaults($contextlevel, $categoryid, $purposeid, $activity, $override);
$this->assertTrue($result);
$targetctxinstance = false;
switch ($contextlevel) {
case CONTEXT_COURSECAT:
$targetctxinstance = $coursecatctxinstance;
break;
case CONTEXT_COURSE:
$targetctxinstance = $coursectxinstance;
break;
case CONTEXT_MODULE:
$targetctxinstance = $assignctxinstance;
break;
case CONTEXT_BLOCK:
$targetctxinstance = $blockctxinstance;
break;
}
$this->assertNotFalse($targetctxinstance);
// Check the context instances.
$instanceexists = context_instance::record_exists($targetctxinstance->get('id'));
if ($override) {
// If overridden, context instances on this context level would have been deleted.
$this->assertFalse($instanceexists);
// Check forum context instance.
$forumctxexists = context_instance::record_exists($forumctxinstance->get('id'));
if ($contextlevel != CONTEXT_MODULE || $foractivity) {
// The forum context instance won't be affected in this test if:
// - The overridden defaults are not for context modules.
// - Only the defaults for assign have been set.
$this->assertTrue($forumctxexists);
} else {
// If we're overriding for the whole course module context level,
// then this forum context instance will be deleted as well.
$this->assertFalse($forumctxexists);
}
} else {
// Otherwise, the context instance record remains.
$this->assertTrue($instanceexists);
}
// Check defaults.
list($defaultpurpose, $defaultcategory) = data_registry::get_defaults($contextlevel, $activity);
if (!$inheritpurpose) {
$this->assertEquals($purposeid, $defaultpurpose);
}
if (!$inheritcategory) {
$this->assertEquals($categoryid, $defaultcategory);
}
}
/**
* Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
*
@@ -0,0 +1,284 @@
<?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/>.
/**
* Step definitions to generate database fixtures for the data privacy tool.
*
* @package tool_dataprivacy
* @category test
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode;
use Behat\Behat\Tester\Exception\PendingException as PendingException;
use tool_dataprivacy\api;
/**
* Step definitions to generate database fixtures for the data privacy tool.
*
* @package tool_dataprivacy
* @category test
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_tool_dataprivacy extends behat_base {
/**
* Each element specifies:
* - The data generator suffix used.
* - The required fields.
* - The mapping between other elements references and database field names.
* @var array
*/
protected static $elements = array(
'categories' => array(
'datagenerator' => 'category',
'required' => array()
),
'purposes' => array(
'datagenerator' => 'purpose',
'required' => array()
),
);
/**
* Creates the specified element. More info about available elements in http://docs.moodle.org/dev/Acceptance_testing#Fixtures.
*
* @Given /^the following data privacy "(?P<element_string>(?:[^"]|\\")*)" exist:$/
*
* @param string $elementname The name of the entity to add
* @param TableNode $data
*/
public function the_following_data_categories_exist($elementname, TableNode $data) {
// Now that we need them require the data generators.
require_once(__DIR__.'/../../../../../lib/phpunit/classes/util.php');
if (empty(self::$elements[$elementname])) {
throw new PendingException($elementname . ' data generator is not implemented');
}
$datagenerator = testing_util::get_data_generator();
$dataprivacygenerator = $datagenerator->get_plugin_generator('tool_dataprivacy');
$elementdatagenerator = self::$elements[$elementname]['datagenerator'];
$requiredfields = self::$elements[$elementname]['required'];
if (!empty(self::$elements[$elementname]['switchids'])) {
$switchids = self::$elements[$elementname]['switchids'];
}
foreach ($data->getHash() as $elementdata) {
// Check if all the required fields are there.
foreach ($requiredfields as $requiredfield) {
if (!isset($elementdata[$requiredfield])) {
throw new Exception($elementname . ' requires the field ' . $requiredfield . ' to be specified');
}
}
// Switch from human-friendly references to ids.
if (isset($switchids)) {
foreach ($switchids as $element => $field) {
$methodname = 'get_' . $element . '_id';
// Not all the switch fields are required, default vars will be assigned by data generators.
if (isset($elementdata[$element])) {
// Temp $id var to avoid problems when $element == $field.
$id = $this->{$methodname}($elementdata[$element]);
unset($elementdata[$element]);
$elementdata[$field] = $id;
}
}
}
// Preprocess the entities that requires a special treatment.
if (method_exists($this, 'preprocess_' . $elementdatagenerator)) {
$elementdata = $this->{'preprocess_' . $elementdatagenerator}($elementdata);
}
// Creates element.
$methodname = 'create_' . $elementdatagenerator;
if (method_exists($dataprivacygenerator, $methodname)) {
// Using data generators directly.
$dataprivacygenerator->{$methodname}($elementdata);
} else if (method_exists($this, 'process_' . $elementdatagenerator)) {
// Using an alternative to the direct data generator call.
$this->{'process_' . $elementdatagenerator}($elementdata);
} else {
throw new PendingException($elementname . ' data generator is not implemented');
}
}
}
/**
* Sets the data category and data storage purpose for the site.
*
* @Given /^I set the site category and purpose to "(?P<category_string>(?:[^"]|\\")*)" and "(?P<purpose_string>(?:[^"]|\\")*)"$/
*
* @param string $category The ID of the category to be set for the instance.
* @param string $purpose The ID of the purpose to be set for the instance.
*/
public function i_set_the_site_category_and_purpose($category, $purpose) {
$category = \tool_dataprivacy\category::get_record(['name' => $category]);
$purpose = \tool_dataprivacy\purpose::get_record(['name' => $purpose]);
$data = (object)[
'contextlevel' => CONTEXT_SYSTEM,
'categoryid' => $category->get('id'),
'purposeid' => $purpose->get('id'),
];
api::set_contextlevel($data);
}
/**
* Sets the data category and data storage purpose for a course category instance.
*
* @Given /^I set the category and purpose for the course category "(?P<categoryname_string>(?:[^"]|\\")*)" to "(?P<category_string>(?:[^"]|\\")*)" and "(?P<purpose_string>(?:[^"]|\\")*)"$/
*
* @param string $name The instance name. It should match the name or the idnumber.
* @param string $category The ID of the category to be set for the instance.
* @param string $purpose The ID of the purpose to be set for the instance.
*/
public function i_set_the_category_and_purpose_for_course_category($name, $category, $purpose) {
global $DB;
$params = [
'name' => $name,
'idnumber' => $name,
];
$select = 'name = :name OR idnumber = :idnumber';
$coursecatid = $DB->get_field_select('course_categories', 'id', $select, $params, MUST_EXIST);
$context = context_coursecat::instance($coursecatid);
$this->set_category_and_purpose($context->id, $category, $purpose);
}
/**
* Sets the data category and data storage purpose for a course instance.
*
* @Given /^I set the category and purpose for the course "(?P<coursename_string>(?:[^"]|\\")*)" to "(?P<category_string>(?:[^"]|\\")*)" and "(?P<purpose_string>(?:[^"]|\\")*)"$/
*
* @param string $name The instance name. It should match the fullname or the shortname, or the idnumber.
* @param string $category The ID of the category to be set for the instance.
* @param string $purpose The ID of the purpose to be set for the instance.
*/
public function i_set_the_category_and_purpose_for_course($name, $category, $purpose) {
global $DB;
$params = [
'shortname' => $name,
'fullname' => $name,
'idnumber' => $name,
];
$select = 'shortname = :shortname OR fullname = :fullname OR idnumber = :idnumber';
$courseid = $DB->get_field_select('course', 'id', $select, $params, MUST_EXIST);
$context = context_course::instance($courseid);
$this->set_category_and_purpose($context->id, $category, $purpose);
}
/**
* Sets the data category and data storage purpose for a course instance.
*
* @Given /^I set the category and purpose for the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" in course "(?P<coursename_string>(?:[^"]|\\")*)" to "(?P<category_string>(?:[^"]|\\")*)" and "(?P<purpose_string>(?:[^"]|\\")*)"$/
*
* @param string $name The instance name. It should match the name of the activity.
* @param string $type The activity type. E.g. assign, quiz, forum, etc.
* @param string $coursename The course name. It should match the fullname or the shortname, or the idnumber.
* @param string $category The ID of the category to be set for the instance.
* @param string $purpose The ID of the purpose to be set for the instance.
*/
public function i_set_the_category_and_purpose_for_activity($name, $type, $coursename, $category, $purpose) {
global $DB;
$params = [
'shortname' => $coursename,
'fullname' => $coursename,
'idnumber' => $coursename,
];
$select = 'shortname = :shortname OR fullname = :fullname OR idnumber = :idnumber';
$courseid = $DB->get_field_select('course', 'id', $select, $params, MUST_EXIST);
$cmid = null;
$cms = get_coursemodules_in_course($type, $courseid);
foreach ($cms as $cm) {
if ($cm->name === $name || $cm->idnumber === $name) {
$cmid = $cm->id;
break;
}
}
if ($cmid === null) {
throw new coding_exception("Activity module '{$name}' of type '{$type}' not found!");
}
$context = context_module::instance($cmid);
$this->set_category_and_purpose($context->id, $category, $purpose);
}
/**
* Sets the data category and data storage purpose for a course instance.
*
* @Given /^I set the category and purpose for the "(?P<blockname_string>(?:[^"]|\\")*)" block in the "(?P<coursename_string>(?:[^"]|\\")*)" course to "(?P<category_string>(?:[^"]|\\")*)" and "(?P<purpose_string>(?:[^"]|\\")*)"$/
*
* @param string $name The instance name. It should match the name of the block. (e.g. online_users)
* @param string $coursename The course name. It should match the fullname or the shortname, or the idnumber.
* @param string $category The ID of the category to be set for the instance.
* @param string $purpose The ID of the purpose to be set for the instance.
*/
public function i_set_the_category_and_purpose_for_block($name, $coursename, $category, $purpose) {
global $DB;
$params = [
'shortname' => $coursename,
'fullname' => $coursename,
'idnumber' => $coursename,
];
$select = 'shortname = :shortname OR fullname = :fullname OR idnumber = :idnumber';
$courseid = $DB->get_field_select('course', 'id', $select, $params, MUST_EXIST);
// Fetch the course context.
$coursecontext = context_course::instance($courseid);
// Fetch the block record and context.
$blockid = $DB->get_field('block_instances', 'id', ['blockname' => $name, 'parentcontextid' => $coursecontext->id]);
$context = context_block::instance($blockid);
// Set the category and purpose.
$this->set_category_and_purpose($context->id, $category, $purpose);
}
/**
* Sets the category and purpose for a context instance.
*
* @param int $contextid The context ID.
* @param int $categoryname The category name.
* @param int $purposename The purpose name.
* @throws coding_exception
*/
protected function set_category_and_purpose($contextid, $categoryname, $purposename) {
$category = \tool_dataprivacy\category::get_record(['name' => $categoryname]);
$purpose = \tool_dataprivacy\purpose::get_record(['name' => $purposename]);
api::set_context_instance((object) [
'contextid' => $contextid,
'purposeid' => $purpose->get('id'),
'categoryid' => $category->get('id'),
]);
}
}
@@ -0,0 +1,298 @@
@tool @tool_dataprivacy @javascript
Feature: Manage data registry defaults
As the privacy officer
In order to manage the data registry
I need to be able to manage the default data categories and data storage purposes for various context levels.
Background:
Given I log in as "admin"
And the following "categories" exist:
| name | idnumber | category |
| Science and technology | scitech | |
| Physics | st-phys | scitech |
And the following "courses" exist:
| fullname | shortname | category |
| Fundamentals of physics 1 | Physics 101 | st-phys |
And the following "activities" exist:
| activity | name | idnumber | course |
| assign | Assignment 1 | assign1 | Physics 101 |
| forum | Forum 1 | forum1 | Physics 101 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| online_users | Course | Physics 101 | course-view-* | site-post |
And the following data privacy "categories" exist:
| name |
| Site category |
| Category 1 |
| Category 2 |
And the following data privacy "purposes" exist:
| name | retentionperiod |
| Site purpose | P10Y |
| Purpose 1 | P3Y |
| Purpose 2 | P5Y |
And I set the site category and purpose to "Site category" and "Site purpose"
Scenario: Set course category data registry defaults
Given I set the category and purpose for the course category "scitech" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I should see "Inherit"
And I should not see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2"
And I should see "5 years"
Scenario: Set course category data registry defaults with override
Given I set the category and purpose for the course category "scitech" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I should see "Inherit"
And I should not see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
And I click on "Reset instances with custom values" "checkbox"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Not set (use the default value)"
And the field "purposeid" matches value "Not set (use the default value)"
And I should see "3 years"
Scenario: Set course data registry defaults
Given I set the category and purpose for the course "Physics 101" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Courses" "link"
And I should see "Inherit"
And I should not see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2"
And I should see "5 years (after the course end date)"
Scenario: Set course data registry defaults with override
Given I set the category and purpose for the course "Physics 101" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Courses" "link"
And I should see "Inherit"
And I should not see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
And I click on "Reset instances with custom values" "checkbox"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Not set (use the default value)"
And the field "purposeid" matches value "Not set (use the default value)"
And I should see "3 years (after the course end date)"
Scenario: Set module level data registry defaults
Given I set the category and purpose for the "assign1" "assign" in course "Physics 101" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Activity modules" "link"
And I should see "Inherit"
And I should see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2"
And I should see "5 years (after the course end date)"
Scenario: Set module level data registry defaults with override
Given I set the category and purpose for the "assign1" "assign" in course "Physics 101" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Activity modules" "link"
And I should see "Inherit"
And I should see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
And I click on "Reset instances with custom values" "checkbox"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Not set (use the default value)"
And the field "purposeid" matches value "Not set (use the default value)"
And I click on "Forum 1 (Forum)" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Not set (use the default value)"
And the field "purposeid" matches value "Not set (use the default value)"
And I should see "3 years (after the course end date)"
Scenario: Set data registry defaults for an activity module
Given I set the category and purpose for the "assign1" "assign" in course "Physics 101" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Activity modules" "link"
And I should see "Inherit"
And I should see "Add a new module default"
And I press "Add a new module default"
And I set the field "Activity module" to "Assignment"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
When I press "Save changes"
Then I should see "Category 1" in the "Assignment" "table_row"
And I should see "Purpose 1" in the "Assignment" "table_row"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2"
And I should see "5 years (after the course end date)"
Scenario: Set data registry defaults for an activity module with override
Given I set the category and purpose for the "assign1" "assign" in course "Physics 101" to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Activity modules" "link"
And I should see "Inherit"
And I should see "Add a new module default"
And I press "Add a new module default"
And I set the field "Activity module" to "Assignment"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
And I click on "Reset instances with custom values" "checkbox"
When I press "Save changes"
Then I should see "Category 1" in the "Assignment" "table_row"
And I should see "Purpose 1" in the "Assignment" "table_row"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Not set (use the default value)"
And the field "purposeid" matches value "Not set (use the default value)"
And I should see "3 years (after the course end date)"
Scenario: Set block category data registry defaults
Given I set the category and purpose for the "online_users" block in the "Physics 101" course to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Blocks" "link"
And I should see "Inherit"
And I should not see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And I click on "Blocks" "link"
And I wait until the page is ready
And I click on "Online users" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2"
And I should see "5 years (after the course end date)"
Scenario: Set course category data registry defaults with override
Given I set the category and purpose for the "online_users" block in the "Physics 101" course to "Category 2" and "Purpose 2"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I click on "Blocks" "link"
And I should see "Inherit"
And I should not see "Add a new module default"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
And I click on "Reset instances with custom values" "checkbox"
When I press "Save changes"
Then I should see "Category 1"
And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link"
And I wait until the page is ready
And I click on "Courses" "link"
And I wait until the page is ready
And I click on "Physics 101" "link"
And I wait until the page is ready
And I click on "Blocks" "link"
And I wait until the page is ready
And I click on "Online users" "link"
And I wait until the page is ready
And the field "categoryid" matches value "Not set (use the default value)"
And the field "purposeid" matches value "Not set (use the default value)"
And I should see "3 years (after the course end date)"
@@ -28,6 +28,7 @@ global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use tool_dataprivacy\api;
use tool_dataprivacy\context_instance;
use tool_dataprivacy\external;
/**
@@ -282,4 +283,281 @@ class tool_dataprivacy_external_testcase extends externallib_advanced_testcase {
$this->expectException(dml_missing_record_exception::class);
external::get_data_request($this->requestid + 1);
}
/**
* Test for \tool_dataprivacy\external::set_context_defaults()
* when called by a user that doesn't have the manage registry capability.
*/
public function test_set_context_defaults_no_capability() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$this->setUser($user);
$this->expectException(required_capability_exception::class);
external::set_context_defaults(CONTEXT_COURSECAT, context_instance::INHERIT, context_instance::INHERIT, '', false);
}
/**
* Test for \tool_dataprivacy\external::set_context_defaults().
*
* We're just checking the module context level here to test the WS function.
* More testing is done in \tool_dataprivacy_api_testcase::test_set_context_defaults().
*
* @dataProvider get_options_provider
* @param bool $modulelevel Whether defaults are to be applied on the module context level or for an activity only.
* @param bool $override Whether to override instances.
*/
public function test_set_context_defaults($modulelevel, $override) {
$this->setAdminUser();
$generator = $this->getDataGenerator();
// Generate course cat, course, block, assignment, forum instances.
$coursecat = $generator->create_category();
$course = $generator->create_course(['category' => $coursecat->id]);
$assign = $generator->create_module('assign', ['course' => $course->id]);
list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
$assigncontext = context_module::instance($assigncm->id);
// Generate purpose and category.
$category1 = api::create_category((object)['name' => 'Test category 1']);
$category2 = api::create_category((object)['name' => 'Test category 2']);
$purpose1 = api::create_purpose((object)[
'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
]);
$purpose2 = api::create_purpose((object)[
'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
]);
// Set a custom purpose and ID for this assignment instance.
$assignctxinstance = api::set_context_instance((object) [
'contextid' => $assigncontext->id,
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
$modulename = $modulelevel ? 'assign' : '';
$categoryid = $category2->get('id');
$purposeid = $purpose2->get('id');
$result = external::set_context_defaults(CONTEXT_MODULE, $categoryid, $purposeid, $modulename, $override);
// Extract the result.
$return = external_api::clean_returnvalue(external::set_context_defaults_returns(), $result);
$this->assertTrue($return['result']);
// Check the assignment context instance.
$instanceexists = context_instance::record_exists($assignctxinstance->get('id'));
if ($override) {
// The custom assign instance should have been deleted.
$this->assertFalse($instanceexists);
} else {
// The custom assign instance should still exist.
$this->assertTrue($instanceexists);
}
// Check the saved defaults.
list($savedpurpose, $savedcategory) = \tool_dataprivacy\data_registry::get_defaults(CONTEXT_MODULE, $modulename);
$this->assertEquals($categoryid, $savedcategory);
$this->assertEquals($purposeid, $savedpurpose);
}
/**
* Test for \tool_dataprivacy\external::get_category_options()
* when called by a user that doesn't have the manage registry capability.
*/
public function test_get_category_options_no_capability() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$this->setUser($user);
$this->expectException(required_capability_exception::class);
external::get_category_options(true, true);
}
/**
* Data provider for \tool_dataprivacy_external_testcase::test_XX_options().
*/
public function get_options_provider() {
return [
[false, false],
[false, true],
[true, false],
[true, true],
];
}
/**
* Test for \tool_dataprivacy\external::get_category_options().
*
* @dataProvider get_options_provider
* @param bool $includeinherit Whether "Inherit" would be included to the options.
* @param bool $includenotset Whether "Not set" would be included to the options.
*/
public function test_get_category_options($includeinherit, $includenotset) {
$this->setAdminUser();
// Prepare our expected options.
$expectedoptions = [];
if ($includeinherit) {
$expectedoptions[] = [
'id' => context_instance::INHERIT,
'name' => get_string('inherit', 'tool_dataprivacy'),
];
}
if ($includenotset) {
$expectedoptions[] = [
'id' => context_instance::NOTSET,
'name' => get_string('notset', 'tool_dataprivacy'),
];
}
for ($i = 1; $i <= 3; $i++) {
$category = api::create_category((object)['name' => 'Category ' . $i]);
$expectedoptions[] = [
'id' => $category->get('id'),
'name' => $category->get('name'),
];
}
// Call the WS function.
$result = external::get_category_options($includeinherit, $includenotset);
// Extract the options.
$return = (object) external_api::clean_returnvalue(external::get_category_options_returns(), $result);
$options = $return->options;
// Make sure everything checks out.
$this->assertCount(count($expectedoptions), $options);
foreach ($options as $option) {
$this->assertContains($option, $expectedoptions);
}
}
/**
* Test for \tool_dataprivacy\external::get_purpose_options()
* when called by a user that doesn't have the manage registry capability.
*/
public function test_get_purpose_options_no_capability() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$this->setUser($user);
$this->expectException(required_capability_exception::class);
external::get_category_options(true, true);
}
/**
* Test for \tool_dataprivacy\external::get_purpose_options().
*
* @dataProvider get_options_provider
* @param bool $includeinherit Whether "Inherit" would be included to the options.
* @param bool $includenotset Whether "Not set" would be included to the options.
*/
public function test_get_purpose_options($includeinherit, $includenotset) {
$this->setAdminUser();
// Prepare our expected options.
$expectedoptions = [];
if ($includeinherit) {
$expectedoptions[] = [
'id' => context_instance::INHERIT,
'name' => get_string('inherit', 'tool_dataprivacy'),
];
}
if ($includenotset) {
$expectedoptions[] = [
'id' => context_instance::NOTSET,
'name' => get_string('notset', 'tool_dataprivacy'),
];
}
for ($i = 1; $i <= 3; $i++) {
$purpose = api::create_purpose((object)[
'name' => 'Purpose ' . $i, 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
]);
$expectedoptions[] = [
'id' => $purpose->get('id'),
'name' => $purpose->get('name'),
];
}
// Call the WS function.
$result = external::get_purpose_options($includeinherit, $includenotset);
// Extract the options.
$return = (object) external_api::clean_returnvalue(external::get_purpose_options_returns(), $result);
$options = $return->options;
// Make sure everything checks out.
$this->assertCount(count($expectedoptions), $options);
foreach ($options as $option) {
$this->assertContains($option, $expectedoptions);
}
}
/**
* Data provider for \tool_dataprivacy_external_testcase::get_activity_options().
*/
public function get_activity_options_provider() {
return [
[false, false, true],
[false, true, true],
[true, false, true],
[true, true, true],
[false, false, false],
[false, true, false],
[true, false, false],
[true, true, false],
];
}
/**
* Test for \tool_dataprivacy\external::get_activity_options().
*
* @dataProvider get_activity_options_provider
* @param bool $inheritcategory Whether the category would be set to "Inherit".
* @param bool $inheritpurpose Whether the purpose would be set to "Inherit".
* @param bool $nodefaults Whether to fetch only activities that don't have defaults.
*/
public function test_get_activity_options($inheritcategory, $inheritpurpose, $nodefaults) {
$this->setAdminUser();
$category = api::create_category((object)['name' => 'Test category']);
$purpose = api::create_purpose((object)[
'name' => 'Test purpose ', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
]);
$categoryid = $category->get('id');
$purposeid = $purpose->get('id');
if ($inheritcategory) {
$categoryid = context_instance::INHERIT;
}
if ($inheritpurpose) {
$purposeid = context_instance::INHERIT;
}
// Set the context default for the assignment module.
api::set_context_defaults(CONTEXT_MODULE, $categoryid, $purposeid, 'assign');
// Call the WS function.
$result = external::get_activity_options($nodefaults);
// Extract the options.
$return = (object) external_api::clean_returnvalue(external::get_activity_options_returns(), $result);
$options = $return->options;
// Make sure the options list is not empty.
$this->assertNotEmpty($options);
$pluginwithdefaults = [
'name' => 'assign',
'displayname' => get_string('pluginname', 'assign')
];
// If we don't want plugins with defaults to be listed or if both of the category and purpose are set to inherit,
// the assign module should be listed.
if (!$nodefaults || ($inheritcategory && $inheritpurpose)) {
$this->assertContains($pluginwithdefaults, $options);
} else {
$this->assertNotContains($pluginwithdefaults, $options);
}
}
}
@@ -0,0 +1,115 @@
<?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 privacy tool data generator.
*
* @package tool_dataprivacy
* @category test
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_dataprivacy\api;
use tool_dataprivacy\category;
use tool_dataprivacy\purpose;
defined('MOODLE_INTERNAL') || die();
/**
* Data privacy tool data generator class.
*
* @package tool_dataprivacy
* @category test
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_dataprivacy_generator extends component_generator_base {
/** @var int Number of created categories. */
protected $categorycount = 0;
/** @var int Number of created purposes. */
protected $purposecount = 0;
/**
* Reset process.
*
* Do not call directly.
*
* @return void
*/
public function reset() {
$this->categorycount = 0;
$this->purposecount = 0;
}
/**
* Create a new category.
*
* @param array|stdClass $record
* @return category
*/
public function create_category($record = null) {
$this->categorycount++;
$i = $this->categorycount;
$record = (object)$record;
if (!isset($record->name)) {
$record->name = "Test purpose $i";
}
if (!isset($record->description)) {
$record->description = "{$record->name} description";
}
$category = api::create_category($record);
return $category;
}
/**
* Create a new purpose.
*
* @param array|stdClass $record
* @return purpose
*/
public function create_purpose($record = null) {
$this->purposecount++;
$i = $this->purposecount;
$record = (object)$record;
if (!isset($record->name)) {
$record->name = "Test purpose $i";
}
if (!isset($record->description)) {
$record->description = "{$record->name} $i description";
}
if (!isset($record->retentionperiod)) {
$record->retentionperiod = 'PT1M';
}
if (!isset($record->lawfulbases)) {
$record->lawfulbases = 'gdpr_art_6_1_a';
}
$purpose = api::create_purpose($record);
return $purpose;
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2018082100;
$plugin->version = 2018091000;
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->component = 'tool_dataprivacy';