MDL-63619 tool_dataprivacy: Fix inheritance from parent contexts

Inheritance should behave such that all contexts inherit from their
parent context.

Prior to this fix, if the value was not set on a context, then it was
getting a default of 'Inherit', but instead of inheritting from the
parent context, it was inheritting from its parent context _level_ which
is just wrong.
This commit is contained in:
Andrew Nicols 2018-10-15 10:45:31 +08:00
parent daf0b4f08b
commit d2aed78933
5 changed files with 471 additions and 276 deletions

View File

@ -914,7 +914,7 @@ class api {
* @param int $forcedvalue Use this categoryid value as if this was this context instance category.
* @return category|false
*/
public static function get_effective_context_category(\context $context, $forcedvalue=false) {
public static function get_effective_context_category(\context $context, $forcedvalue = false) {
if (!data_registry::defaults_set()) {
return false;
}
@ -941,15 +941,14 @@ class api {
* Returns the effective category given a context level.
*
* @param int $contextlevel
* @param int $forcedvalue Use this categoryid value as if this was this context level category.
* @return category|false
*/
public static function get_effective_contextlevel_category($contextlevel, $forcedvalue=false) {
public static function get_effective_contextlevel_category($contextlevel) {
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue);
return data_registry::get_effective_contextlevel_value($contextlevel, 'category');
}
/**

View File

@ -39,18 +39,6 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_registry {
/**
* @var array Inheritance between context levels.
*/
private static $contextlevelinheritance = [
CONTEXT_USER => [CONTEXT_SYSTEM],
CONTEXT_COURSECAT => [CONTEXT_SYSTEM],
CONTEXT_COURSE => [CONTEXT_COURSECAT, CONTEXT_SYSTEM],
CONTEXT_MODULE => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
CONTEXT_BLOCK => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
];
/**
* Returns purpose and category var names from a context class name
*
@ -83,7 +71,6 @@ class data_registry {
* @return int[]|false[]
*/
public static function get_defaults($contextlevel, $pluginname = '') {
$classname = \context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
@ -104,10 +91,10 @@ class data_registry {
}
if (empty($purposeid)) {
$purposeid = false;
$purposeid = context_instance::NOTSET;
}
if (empty($categoryid)) {
$categoryid = false;
$categoryid = context_instance::NOTSET;
}
return [$purposeid, $categoryid];
@ -190,19 +177,24 @@ class data_registry {
* @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element
*/
public static function get_effective_context_value(\context $context, $element, $forcedvalue = false) {
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
}
$fieldname = $element . 'id';
if (empty($forcedvalue)) {
$instance = context_instance::get_record_by_contextid($context->id, false);
// Check whether this context is a user context, or a child of a user context.
// User contexts share the same context and cannot be set individually.
$parents = $context->get_parent_contexts(true);
foreach ($parents as $parent) {
if ($parent->contextlevel == CONTEXT_USER) {
// Use the context level value for the user.
return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
}
}
if (!$instance) {
// If the instance does not have a value defaults to not set, so we grab the context level default as its value.
$instancevalue = context_instance::NOTSET;
} else {
$instancevalue = context_instance::NOTSET;
if (empty($forcedvalue)) {
if ($instance = context_instance::get_record_by_contextid($context->id, false)) {
$instancevalue = $instance->get($fieldname);
}
} else {
@ -211,48 +203,34 @@ class data_registry {
// Not set.
if ($instancevalue == context_instance::NOTSET) {
// The effective value varies depending on the context level.
if ($context->contextlevel == CONTEXT_USER) {
// Use the context level value as we don't allow people to set specific instances values.
return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
}
$parents = $context->get_parent_contexts(true);
foreach ($parents as $parent) {
if ($parent->contextlevel == CONTEXT_USER) {
// Use the context level value as we don't allow people to set specific instances values.
return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
}
}
// 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, false, false, $forplugin
);
return self::get_element_instance($element, $$fieldname);
$instancevalue = $$fieldname;
}
// Specific value for this context instance.
if ($instancevalue != context_instance::INHERIT) {
if (context_instance::NOTSET !== $instancevalue && context_instance::INHERIT !== $instancevalue) {
// There is an actual value. Return it.
return self::get_element_instance($element, $instancevalue);
}
// This context is using inherited so let's return the parent effective value.
$parentcontext = $context->get_parent_context();
if (!$parentcontext) {
return false;
}
// There is no value set (or it is set to inherit).
// Fetch from the parent context.
$parent = $context->get_parent_context();
// The forced value should not be transmitted to parent contexts.
return self::get_effective_context_value($parentcontext, $element);
if (CONTEXT_SYSTEM == $parent->contextlevel) {
return self::get_effective_contextlevel_value(CONTEXT_SYSTEM, $element);
} else {
return self::get_effective_context_value($context->get_parent_context(), $element);
}
}
/**
@ -264,11 +242,9 @@ class data_registry {
*
* @param int $contextlevel
* @param string $element 'category' or 'purpose'
* @param int $forcedvalue Use this value as if this was this context level purpose.
* @return \tool_dataprivacy\purpose|false
*/
public static function get_effective_contextlevel_value($contextlevel, $element, $forcedvalue = false) {
public static function get_effective_contextlevel_value($contextlevel, $element) {
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
}
@ -279,39 +255,15 @@ class data_registry {
'have a purpose or a category.');
}
if ($forcedvalue === false) {
$instance = contextlevel::get_record_by_contextlevel($contextlevel, false);
if (!$instance) {
// If the context level does not have a value defaults to not set, so we grab the context level default as
// its value.
$instancevalue = context_instance::NOTSET;
} else {
$instancevalue = $instance->get($fieldname);
}
} else {
$instancevalue = $forcedvalue;
}
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
// Not set -> Use the default context level value.
if ($instancevalue == context_instance::NOTSET) {
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
// Note: The $$fieldname points to either $purposeid, or $categoryid.
if (context_instance::NOTSET !== $$fieldname && context_instance::INHERIT !== $$fieldname) {
// There is a specific value set.
return self::get_element_instance($element, $$fieldname);
}
// Specific value for this context instance.
if ($instancevalue != context_instance::INHERIT) {
return self::get_element_instance($element, $instancevalue);
}
if ($contextlevel == CONTEXT_SYSTEM) {
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
// If we reach this point is that we are inheriting so get the parent context level and repeat.
$parentcontextlevel = reset(self::$contextlevelinheritance[$contextlevel]);
// Forced value are intentionally not passed as the force value should only affect the immediate context level.
return self::get_effective_contextlevel_value($parentcontextlevel, $element);
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
/**
@ -320,13 +272,13 @@ class data_registry {
* @param int $contextlevel
* @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.
* @param string $component The name of the component to check.
* @return int[]
*/
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
$forcedcategoryvalue = false, $activity = '') {
list($purposeid, $categoryid) = self::get_defaults($contextlevel, $activity);
$forcedcategoryvalue = false, $component = '') {
// Get the defaults for this context level.
list($purposeid, $categoryid) = self::get_defaults($contextlevel, $component);
// Honour forced values.
if ($forcedpurposevalue) {
@ -336,37 +288,19 @@ class data_registry {
$categoryid = $forcedcategoryvalue;
}
// Not set == INHERIT for defaults.
if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) {
$purposeid = false;
}
if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
$categoryid = false;
}
if ($contextlevel == CONTEXT_USER) {
// Only user context levels inherit from a parent context level.
list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
$purposeid = $parentpurposeid;
}
list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent);
// Not set == INHERIT for defaults.
if ($parentpurposeid == context_instance::INHERIT || $parentpurposeid == context_instance::NOTSET) {
$parentpurposeid = false;
}
if ($parentcategoryid == context_instance::INHERIT || $parentcategoryid == context_instance::NOTSET) {
$parentcategoryid = false;
}
if ($purposeid === false && $parentpurposeid) {
$purposeid = $parentpurposeid;
}
if ($categoryid === false && $parentcategoryid) {
$categoryid = $parentcategoryid;
}
if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
$categoryid = $parentcategoryid;
}
}
// They may still be false, but we return anyway.
return [$purposeid, $categoryid];
}
@ -379,7 +313,6 @@ class data_registry {
* @return \core\persistent
*/
private static function get_element_instance($element, $id) {
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('No other elements than purpose and category are allowed');
}

View File

@ -1150,11 +1150,6 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->assertEquals($purposes[0]->get('id'), $purposeid);
$this->assertEquals(false, $categoryid);
// Course inherits from system if not defined.
list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
$this->assertEquals($purposes[0]->get('id'), $purposeid);
$this->assertEquals(false, $categoryid);
// Course defined values should have preference.
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
\context_helper::get_class_for_level(CONTEXT_COURSE)
@ -1168,159 +1163,293 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
// Context level defaults are also allowed to be set to 'inherit'.
set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
$this->assertEquals($purposes[0]->get('id'), $purposeid);
$this->assertEquals($categories[0]->get('id'), $categoryid);
list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
$this->assertEquals($purposes[0]->get('id'), $purposeid);
$this->assertEquals($categories[0]->get('id'), $categoryid);
}
public function test_get_effective_contextlevel_category() {
/**
* Ensure that when nothing is configured, all values return false.
*/
public function test_get_effective_contextlevel_unset() {
// Before setup, get_effective_contextlevel_purpose will return false.
$this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
$this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
$this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_USER));
$this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_USER));
}
/**
* Ensure that when nothing is configured, all values return false.
*/
public function test_get_effective_context_unset() {
// Before setup, get_effective_contextlevel_purpose will return false.
$this->assertFalse(api::get_effective_context_category(\context_system::instance()));
$this->assertFalse(api::get_effective_context_purpose(\context_system::instance()));
}
/**
* Ensure that fetching the effective value for context levels is only available to system, and user context levels.
*
* @dataProvider invalid_effective_contextlevel_provider
* @param int $contextlevel
*/
public function test_set_contextlevel_invalid_contextlevels($contextlevel) {
$this->expectException(coding_exception::class);
api::set_contextlevel((object) [
'contextlevel' => $contextlevel,
]);
}
/**
* Test effective contextlevel return.
*/
public function test_effective_contextlevel() {
$this->setAdminUser();
$this->resetAfterTest();
// Before setup, get_effective_contextlevel_purpose will return false.
$this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
// Set the system context level to purpose 1.
$record = (object)[
// Set the initial purpose and category.
$purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
$category1 = api::create_category((object)['name' => 'a']);
api::set_contextlevel((object)[
'contextlevel' => CONTEXT_SYSTEM,
'purposeid' => $purposes[1]->get('id'),
'categoryid' => $categories[1]->get('id'),
];
api::set_contextlevel($record);
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
$this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
$this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
$this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
// Value 'not set' will get the default value for the context level. For context level defaults
// both 'not set' and 'inherit' result in inherit, so the parent context (system) default
// will be retrieved.
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
$this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
// The user context inherits from the system context when not set.
$this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
$this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
// The behaviour forcing an inherit from context system should result in the same effective
// purpose.
$record->purposeid = context_instance::INHERIT;
$record->contextlevel = CONTEXT_USER;
api::set_contextlevel($record);
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
$this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
// Forcing the behaviour to inherit will have the same result.
api::set_contextlevel((object) [
'contextlevel' => CONTEXT_USER,
'purposeid' => context_instance::INHERIT,
'categoryid' => context_instance::INHERIT,
]);
$this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
$this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
$record->purposeid = $purposes[2]->get('id');
$record->contextlevel = CONTEXT_USER;
api::set_contextlevel($record);
// Setting specific values will override the inheritance behaviour.
$purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_a']);
$category2 = api::create_category((object)['name' => 'b']);
// Set the system context level to purpose 1.
api::set_contextlevel((object) [
'contextlevel' => CONTEXT_USER,
'purposeid' => $purpose2->get('id'),
'categoryid' => $category2->get('id'),
]);
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
$this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
// Only system and user allowed.
$this->expectException(coding_exception::class);
$record->contextlevel = CONTEXT_COURSE;
$record->purposeid = $purposes[1]->get('id');
api::set_contextlevel($record);
$this->assertEquals($purpose2, api::get_effective_contextlevel_purpose(CONTEXT_USER));
$this->assertEquals($category2, api::get_effective_contextlevel_category(CONTEXT_USER));
}
/**
* Test effective context purposes and categories.
* Ensure that fetching the effective value for context levels is only available to system, and user context levels.
*
* @return null
* @dataProvider invalid_effective_contextlevel_provider
* @param int $contextlevel
*/
public function test_effective_context() {
public function test_effective_contextlevel_invalid_contextlevels($contextlevel) {
$this->resetAfterTest();
$this->setAdminUser();
$purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
$category1 = api::create_category((object)['name' => 'a']);
api::set_contextlevel((object)[
'contextlevel' => CONTEXT_SYSTEM,
'purposeid' => $purpose1->get('id'),
'categoryid' => $category1->get('id'),
]);
list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
$this->expectException(coding_exception::class);
api::get_effective_contextlevel_purpose($contextlevel);
}
// Define system defaults (all context levels below will inherit).
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
\context_helper::get_class_for_level(CONTEXT_SYSTEM)
);
set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
// Define course defaults.
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
\context_helper::get_class_for_level(CONTEXT_COURSE)
);
set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
$course0context = \context_course::instance($courses[0]->id);
$course1context = \context_course::instance($courses[1]->id);
$mod0context = \context_module::instance($modules[0]->cmid);
$mod1context = \context_module::instance($modules[1]->cmid);
// Set course instance values.
$record = (object)[
'contextid' => $course0context->id,
'purposeid' => $purposes[1]->get('id'),
'categoryid' => $categories[2]->get('id'),
/**
* Data provider for invalid contextlevel fetchers.
*/
public function invalid_effective_contextlevel_provider() {
return [
[CONTEXT_COURSECAT],
[CONTEXT_COURSE],
[CONTEXT_MODULE],
[CONTEXT_BLOCK],
];
api::set_context_instance($record);
$category = api::get_effective_context_category($course0context);
$this->assertEquals($record->categoryid, $category->get('id'));
}
// Module instances get the context level default if nothing specified.
$category = api::get_effective_context_category($mod0context);
$this->assertEquals($categories[1]->get('id'), $category->get('id'));
/**
* Ensure that context inheritance works up the context tree.
*/
public function test_effective_context_inheritance() {
$this->resetAfterTest();
// Module instances get the parent context category if they inherit.
$record->contextid = $mod0context->id;
$record->categoryid = context_instance::INHERIT;
api::set_context_instance($record);
$category = api::get_effective_context_category($mod0context);
$this->assertEquals($categories[2]->get('id'), $category->get('id'));
$systemdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_SYSTEM);
// The $forcedvalue param allows us to override the actual value (method php-docs for more info).
$category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
$this->assertEquals($categories[1]->get('id'), $category->get('id'));
$category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
$this->assertEquals($categories[0]->get('id'), $category->get('id'));
/*
* System
* - Cat
* - Subcat
* - Course
* - Forum
* - User
* - User block
*/
$cat = $this->getDataGenerator()->create_category();
$subcat = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
$course = $this->getDataGenerator()->create_course(['category' => $subcat->id]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
// Module instances get the parent context category if they inherit; in
// this case the parent context category is not set so it should use the
// context level default (see 'Define course defaults' above).
$record->contextid = $mod1context->id;
$record->categoryid = context_instance::INHERIT;
api::set_context_instance($record);
$category = api::get_effective_context_category($mod1context);
$this->assertEquals($categories[1]->get('id'), $category->get('id'));
$user = $this->getDataGenerator()->create_user();
// User instances use the value set at user context level instead of the user default.
$contextsystem = \context_system::instance();
$contextcat = \context_coursecat::instance($cat->id);
$contextsubcat = \context_coursecat::instance($subcat->id);
$contextcourse = \context_course::instance($course->id);
$contextforum = \context_module::instance($forumcm->id);
$contextuser = \context_user::instance($user->id);
// User defaults to cat 0 and user context level to 1.
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
\context_helper::get_class_for_level(CONTEXT_USER)
);
set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
$usercontextlevel = (object)[
'contextlevel' => CONTEXT_USER,
'purposeid' => $purposes[1]->get('id'),
'categoryid' => $categories[1]->get('id'),
];
api::set_contextlevel($usercontextlevel);
// Initially everything is set to Inherit.
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser));
$newuser = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($newuser->id);
$category = api::get_effective_context_category($usercontext);
$this->assertEquals($categories[1]->get('id'), $category->get('id'));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser));
// When actively set, user will use the specified value.
$userdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_USER);
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
$this->assertEquals($userdata->purpose, api::get_effective_context_purpose($contextuser));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
$this->assertEquals($userdata->category, api::get_effective_context_category($contextuser));
// Set a context for the top category.
$catpurpose = new purpose(0, (object) [
'name' => 'Purpose',
'retentionperiod' => 'P1D',
'lawfulbases' => 'gdpr_art_6_1_a',
]);
$catpurpose->save();
$catcategory = new category(0, (object) ['name' => 'Category']);
$catcategory->save();
api::set_context_instance((object) [
'contextid' => $contextcat->id,
'purposeid' => $catpurpose->get('id'),
'categoryid' => $catcategory->get('id'),
]);
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat));
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse));
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
$this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat));
$this->assertEquals($catcategory, api::get_effective_context_category($contextcourse));
$this->assertEquals($catcategory, api::get_effective_context_category($contextforum));
// Set a context for the sub category.
$subcatpurpose = new purpose(0, (object) [
'name' => 'Purpose',
'retentionperiod' => 'P1D',
'lawfulbases' => 'gdpr_art_6_1_a',
]);
$subcatpurpose->save();
$subcatcategory = new category(0, (object) ['name' => 'Category']);
$subcatcategory->save();
api::set_context_instance((object) [
'contextid' => $contextsubcat->id,
'purposeid' => $subcatpurpose->get('id'),
'categoryid' => $subcatcategory->get('id'),
]);
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse));
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse));
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum));
// Set a context for the course.
$coursepurpose = new purpose(0, (object) [
'name' => 'Purpose',
'retentionperiod' => 'P1D',
'lawfulbases' => 'gdpr_art_6_1_a',
]);
$coursepurpose->save();
$coursecategory = new category(0, (object) ['name' => 'Category']);
$coursecategory->save();
api::set_context_instance((object) [
'contextid' => $contextcourse->id,
'purposeid' => $coursepurpose->get('id'),
'categoryid' => $coursecategory->get('id'),
]);
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
$this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
$this->assertEquals($coursecategory, api::get_effective_context_category($contextforum));
// Set a context for the forum.
$forumpurpose = new purpose(0, (object) [
'name' => 'Purpose',
'retentionperiod' => 'P1D',
'lawfulbases' => 'gdpr_art_6_1_a',
]);
$forumpurpose->save();
$forumcategory = new category(0, (object) ['name' => 'Category']);
$forumcategory->save();
api::set_context_instance((object) [
'contextid' => $contextforum->id,
'purposeid' => $forumpurpose->get('id'),
'categoryid' => $forumcategory->get('id'),
]);
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
$this->assertEquals($forumpurpose, api::get_effective_context_purpose($contextforum));
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
$this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
$this->assertEquals($forumcategory, api::get_effective_context_category($contextforum));
}
/**
@ -1388,7 +1517,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
@ -1404,7 +1533,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
$requests = contextlist_context::get_records();
@ -1420,7 +1549,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
@ -1436,7 +1565,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
$requests = contextlist_context::get_records();
@ -1452,7 +1581,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
@ -1468,7 +1597,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
$purposes->course->set('protected', 0)->save();
$purposes->course->purpose->set('protected', 0)->save();
api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
$requests = contextlist_context::get_records();
@ -1482,7 +1611,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
@ -1508,7 +1637,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
]);
$rcl->save();
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$collection = api::get_approved_contextlist_collection_for_request($request);
$this->assertCount(1, $collection);
@ -1524,7 +1653,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
@ -1550,7 +1679,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
]);
$rcl->save();
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$collection = api::get_approved_contextlist_collection_for_request($request);
$this->assertCount(0, $collection);
@ -1566,7 +1695,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
$purposes->course->set('protected', 1)->save();
$purposes->course->purpose->set('protected', 1)->save();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
@ -1592,7 +1721,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
]);
$rcl->save();
$purposes->course->set('protected', 0)->save();
$purposes->course->purpose->set('protected', 0)->save();
$collection = api::get_approved_contextlist_collection_for_request($request);
$this->assertCount(1, $collection);
@ -1893,9 +2022,8 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
*
* @param string $retention
* @param int $contextlevel
* @return purpose
*/
protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) : purpose {
protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) {
$purpose = new purpose(0, (object) [
'name' => 'Test purpose ' . rand(1, 1000),
'retentionperiod' => $retention,
@ -1920,6 +2048,9 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
}
return $purpose;
return (object) [
'purpose' => $purpose,
'category' => $cat,
];
}
}

View File

@ -32,12 +32,11 @@ Feature: Manage data registry defaults
| Purpose 2 | P5Y |
And I set the site category and purpose to "Site category" and "Site purpose"
# Setting a default for course categories should apply to everything beneath that category.
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
Given 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"
@ -47,29 +46,93 @@ Feature: Manage data registry defaults
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"
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 should see "3 years"
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I should see "3 years"
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And I should see "3 years"
# When Setting a default for course categories, and overriding a specific category, only that category and its
# children will be overridden.
# If any child is a course category, it will get the default.
Scenario: Set course category data registry defaults with override
Given I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link"
And I press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
And I press "Save changes"
And I should see "Category 1"
And I should see "Purpose 1"
And I set the category and purpose for the course category "scitech" to "Category 2" and "Purpose 2"
When 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
Then the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2"
And I should see "5 years"
And I click on "Courses" "link"
And I wait until the page is ready
# Physics 101 is also a category, so it will get the category default.
And I click on "Physics 101" "link"
And I wait until the page is ready
And I should see "3 years"
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I should see "3 years"
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And I should see "3 years"
# When overriding a specific category, only that category and its children will be overridden.
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"
When 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
Then the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2"
And I should see "5 years"
And I click on "Courses" "link"
And I wait until the page is ready
# Physics 101 is also a category, so it will get the category default.
And I click on "Physics 101" "link"
And I wait until the page is ready
And I should see "5 years"
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I should see "5 years"
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And I should see "5 years"
# Resetting instances removes custom values.
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 press "Edit"
And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1"
When I click on "Reset instances with custom values" "checkbox"
And I press "Save changes"
And 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
Then 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"
@ -94,6 +157,12 @@ Feature: Manage data registry defaults
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)"
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I should see "5 years"
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And I should see "5 years"
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"
@ -119,6 +188,12 @@ Feature: Manage data registry defaults
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)"
And I click on "Activities and resources" "link"
And I wait until the page is ready
And I should see "3 years"
And I click on "Assignment 1 (Assignment)" "link"
And I wait until the page is ready
And I should see "3 years"
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"

View File

@ -0,0 +1,57 @@
<?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/>.
/**
* Unit tests for the data_registry class.
*
* @package tool_dataprivacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \tool_dataprivacy\data_registry;
/**
* Unit tests for the data_registry class.
*
* @package tool_dataprivacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_dataprivacy_dataregistry_testcase extends advanced_testcase {
/**
* Ensure that the get_effective_context_value only errors if provided an inappropriate element.
*
* This test is not great because we only test a limited set of values. This is a fault of the underlying API.
*/
public function test_get_effective_context_value_invalid_element() {
$this->expectException(coding_exception::class);
data_registry::get_effective_context_value(\context_system::instance(), 'invalid');
}
/**
* Ensure that the get_effective_contextlevel_value only errors if provided an inappropriate element.
*
* This test is not great because we only test a limited set of values. This is a fault of the underlying API.
*/
public function test_get_effective_contextlevel_value_invalid_element() {
$this->expectException(coding_exception::class);
data_registry::get_effective_contextlevel_value(\context_system::instance(), 'invalid');
}
}