Commit f560f366 authored by David Monllaó's avatar David Monllaó
Browse files

Merge branch 'MDL-63619-master' of git://github.com/andrewnicols/moodle

parents 66de50c3 d95ad08f
...@@ -914,7 +914,7 @@ class api { ...@@ -914,7 +914,7 @@ class api {
* @param int $forcedvalue Use this categoryid value as if this was this context instance category. * @param int $forcedvalue Use this categoryid value as if this was this context instance category.
* @return category|false * @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()) { if (!data_registry::defaults_set()) {
return false; return false;
} }
...@@ -941,15 +941,14 @@ class api { ...@@ -941,15 +941,14 @@ class api {
* Returns the effective category given a context level. * Returns the effective category given a context level.
* *
* @param int $contextlevel * @param int $contextlevel
* @param int $forcedvalue Use this categoryid value as if this was this context level category.
* @return category|false * @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()) { if (!data_registry::defaults_set()) {
return false; return false;
} }
return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue); return data_registry::get_effective_contextlevel_value($contextlevel, 'category');
} }
/** /**
......
...@@ -39,18 +39,6 @@ defined('MOODLE_INTERNAL') || die(); ...@@ -39,18 +39,6 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
class data_registry { 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 * Returns purpose and category var names from a context class name
* *
...@@ -83,7 +71,6 @@ class data_registry { ...@@ -83,7 +71,6 @@ class data_registry {
* @return int[]|false[] * @return int[]|false[]
*/ */
public static function get_defaults($contextlevel, $pluginname = '') { public static function get_defaults($contextlevel, $pluginname = '') {
$classname = \context_helper::get_class_for_level($contextlevel); $classname = \context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname); list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
...@@ -104,10 +91,10 @@ class data_registry { ...@@ -104,10 +91,10 @@ class data_registry {
} }
if (empty($purposeid)) { if (empty($purposeid)) {
$purposeid = false; $purposeid = context_instance::NOTSET;
} }
if (empty($categoryid)) { if (empty($categoryid)) {
$categoryid = false; $categoryid = context_instance::NOTSET;
} }
return [$purposeid, $categoryid]; return [$purposeid, $categoryid];
...@@ -190,69 +177,92 @@ class data_registry { ...@@ -190,69 +177,92 @@ class data_registry {
* @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element * @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) { public static function get_effective_context_value(\context $context, $element, $forcedvalue = false) {
global $DB;
if ($element !== 'purpose' && $element !== 'category') { if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.'); throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
} }
$fieldname = $element . 'id'; $fieldname = $element . 'id';
if (empty($forcedvalue)) { if (!empty($forcedvalue) && ($forcedvalue === context_instance::INHERIT)) {
$instance = context_instance::get_record_by_contextid($context->id, false); // Do not include the current context when calculating the value.
// This has the effect that an inheritted value is calculated.
if (!$instance) { $parentcontextids = $context->get_parent_context_ids(false);
// If the instance does not have a value defaults to not set, so we grab the context level default as its value. } else if (!empty($forcedvalue) && ($forcedvalue !== context_instance::NOTSET)) {
$instancevalue = context_instance::NOTSET; return self::get_element_instance($element, $forcedvalue);
} else {
$instancevalue = $instance->get($fieldname);
}
} else { } else {
$instancevalue = $forcedvalue; // Fetch all parent contexts, including self.
$parentcontextids = $context->get_parent_context_ids(true);
} }
list($insql, $inparams) = $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED);
$inparams['contextmodule'] = CONTEXT_MODULE;
// Not set. if ('purpose' === $element) {
if ($instancevalue == context_instance::NOTSET) { $elementjoin = 'LEFT JOIN {tool_dataprivacy_purpose} ele ON ctxins.purposeid = ele.id';
$elementfields = purpose::get_sql_fields('ele', 'ele');
// The effective value varies depending on the context level. } else {
if ($context->contextlevel == CONTEXT_USER) { $elementjoin = 'LEFT JOIN {tool_dataprivacy_category} ele ON ctxins.categoryid = ele.id';
// Use the context level value as we don't allow people to set specific instances values. $elementfields = category::get_sql_fields('ele', 'ele');
}
$contextfields = \context_helper::get_preload_record_columns_sql('ctx');
$fields = implode(', ', ['ctx.id', 'm.name AS modname', $contextfields, $elementfields]);
$sql = "SELECT $fields
FROM {context} ctx
LEFT JOIN {tool_dataprivacy_ctxinstance} ctxins ON ctx.id = ctxins.contextid
LEFT JOIN {course_modules} cm ON ctx.contextlevel = :contextmodule AND ctx.instanceid = cm.id
LEFT JOIN {modules} m ON m.id = cm.module
{$elementjoin}
WHERE ctx.id {$insql}
ORDER BY ctx.path DESC";
$contextinstances = $DB->get_records_sql($sql, $inparams);
// Check whether this context is a user context, or a child of a user context.
// All children of a User context share the same context and cannot be set individually.
foreach ($contextinstances as $record) {
\context_helper::preload_from_record($record);
$parent = \context::instance_by_id($record->id, false);
if ($parent->contextlevel == CONTEXT_USER) {
// Use the context level value for the user.
return self::get_effective_contextlevel_value(CONTEXT_USER, $element); return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
} }
}
$parents = $context->get_parent_contexts(true); foreach ($contextinstances as $record) {
foreach ($parents as $parent) { $parent = \context::instance_by_id($record->id, false);
if ($parent->contextlevel == CONTEXT_USER) {
// Use the context level value as we don't allow people to set specific instances values. $checkcontextlevel = false;
return self::get_effective_contextlevel_value(CONTEXT_USER, $element); if (empty($record->eleid)) {
} $checkcontextlevel = true;
} }
// Check if we need to pass the plugin name of an activity. if (!empty($forcedvalue) && context_instance::NOTSET === $forcedvalue) {
$forplugin = ''; $checkcontextlevel = true;
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); if ($checkcontextlevel) {
} // Check for a value at the contextlevel
$forplugin = empty($record->modname) ? '' : $record->modname;
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
$parent->contextlevel, false, false, $forplugin);
// Specific value for this context instance. $instancevalue = $$fieldname;
if ($instancevalue != context_instance::INHERIT) {
return self::get_element_instance($element, $instancevalue);
}
// This context is using inherited so let's return the parent effective value. if (context_instance::NOTSET !== $instancevalue && context_instance::INHERIT !== $instancevalue) {
$parentcontext = $context->get_parent_context(); // There is an actual value. Return it.
if (!$parentcontext) { return self::get_element_instance($element, $instancevalue);
return false; }
} else {
$elementclass = "\\tool_dataprivacy\\{$element}";
$instance = new $elementclass(null, $elementclass::extract_record($record, 'ele'));
$instance->validate();
return $instance;
}
} }
// The forced value should not be transmitted to parent contexts. throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
return self::get_effective_context_value($parentcontext, $element);
} }
/** /**
...@@ -264,11 +274,9 @@ class data_registry { ...@@ -264,11 +274,9 @@ class data_registry {
* *
* @param int $contextlevel * @param int $contextlevel
* @param string $element 'category' or 'purpose' * @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 * @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') { if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.'); throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
} }
...@@ -279,39 +287,15 @@ class data_registry { ...@@ -279,39 +287,15 @@ class data_registry {
'have a purpose or a category.'); 'have a purpose or a category.');
} }
if ($forcedvalue === false) { list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
$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;
}
// Not set -> Use the default context level value. // Note: The $$fieldname points to either $purposeid, or $categoryid.
if ($instancevalue == context_instance::NOTSET) { if (context_instance::NOTSET !== $$fieldname && context_instance::INHERIT !== $$fieldname) {
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel); // There is a specific value set.
return self::get_element_instance($element, $$fieldname); return self::get_element_instance($element, $$fieldname);
} }
// Specific value for this context instance. throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
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);
} }
/** /**
...@@ -320,13 +304,13 @@ class data_registry { ...@@ -320,13 +304,13 @@ class data_registry {
* @param int $contextlevel * @param int $contextlevel
* @param int|bool $forcedpurposevalue Use this value as if this was this context level purpose. * @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 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[] * @return int[]
*/ */
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false, public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
$forcedcategoryvalue = false, $activity = '') { $forcedcategoryvalue = false, $component = '') {
// Get the defaults for this context level.
list($purposeid, $categoryid) = self::get_defaults($contextlevel, $activity); list($purposeid, $categoryid) = self::get_defaults($contextlevel, $component);
// Honour forced values. // Honour forced values.
if ($forcedpurposevalue) { if ($forcedpurposevalue) {
...@@ -336,37 +320,19 @@ class data_registry { ...@@ -336,37 +320,19 @@ class data_registry {
$categoryid = $forcedcategoryvalue; $categoryid = $forcedcategoryvalue;
} }
// Not set == INHERIT for defaults. if ($contextlevel == CONTEXT_USER) {
if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) { // Only user context levels inherit from a parent context level.
$purposeid = false; list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
}
if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
$categoryid = false;
}
if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent); if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
// Not set == INHERIT for defaults. $purposeid = $parentpurposeid;
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) { if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
$categoryid = $parentcategoryid; $categoryid = $parentcategoryid;
}
} }
} }
// They may still be false, but we return anyway.
return [$purposeid, $categoryid]; return [$purposeid, $categoryid];
} }
...@@ -379,7 +345,6 @@ class data_registry { ...@@ -379,7 +345,6 @@ class data_registry {
* @return \core\persistent * @return \core\persistent
*/ */
private static function get_element_instance($element, $id) { private static function get_element_instance($element, $id) {
if ($element !== 'purpose' && $element !== 'category') { if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('No other elements than purpose and category are allowed'); throw new coding_exception('No other elements than purpose and category are allowed');
} }
......
...@@ -145,12 +145,12 @@ class context_instance extends \core\form\persistent { ...@@ -145,12 +145,12 @@ class context_instance extends \core\form\persistent {
$persistent->set('contextid', $context->id); $persistent->set('contextid', $context->id);
} }
$purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options( $purposes = [];
api::get_purposes() foreach (api::get_purposes() as $purpose) {
); $purposes[$purpose->get('id')] = $purpose;
$categoryoptions = \tool_dataprivacy\output\data_registry_page::category_options( }
api::get_categories() $purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options($purposes);
); $categoryoptions = \tool_dataprivacy\output\data_registry_page::category_options(api::get_categories());
$customdata = [ $customdata = [
'context' => $context, 'context' => $context,
...@@ -168,9 +168,14 @@ class context_instance extends \core\form\persistent { ...@@ -168,9 +168,14 @@ class context_instance extends \core\form\persistent {
$context); $context);
$customdata['purposeretentionperiods'] = []; $customdata['purposeretentionperiods'] = [];
foreach ($purposeoptions as $optionvalue => $unused) { foreach (array_keys($purposeoptions) as $optionvalue) {
// Get the effective purpose if $optionvalue would be the selected value.
$purpose = api::get_effective_context_purpose($context, $optionvalue); if (isset($purposes[$optionvalue])) {
$purpose = $purposes[$optionvalue];
} else {
// Get the effective purpose if $optionvalue would be the selected value.
$purpose = api::get_effective_context_purpose($context, $optionvalue);
}
$retentionperiod = self::get_retention_display_text( $retentionperiod = self::get_retention_display_text(
$purpose, $purpose,
......
This diff is collapsed.
...@@ -32,12 +32,11 @@ Feature: Manage data registry defaults ...@@ -32,12 +32,11 @@ Feature: Manage data registry defaults
| Purpose 2 | P5Y | | Purpose 2 | P5Y |
And I set the site category and purpose to "Site category" and "Site purpose" 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 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" Given I navigate to "Users > Privacy and policies > Data registry" in site administration
And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link" And I click on "Set defaults" "link"
And I should see "Inherit" And I should see "Inherit"
And I should not see "Add a new module default"
And I press "Edit" And I press "Edit"
And I set the field "Category" to "Category 1" And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1" And I set the field "Purpose" to "Purpose 1"
...@@ -47,27 +46,91 @@ Feature: Manage data registry defaults ...@@ -47,27 +46,91 @@ Feature: Manage data registry defaults
And I navigate to "Users > Privacy and policies > Data registry" in site administration And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link" And I click on "Science and technology" "link"
And I wait until the page is ready And I wait until the page is ready
And the field "categoryid" matches value "Category 2" 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 the field "purposeid" matches value "Purpose 2"
And I should see "5 years" 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 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" 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 navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Set defaults" "link" 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 press "Edit"
And I set the field "Category" to "Category 1" And I set the field "Category" to "Category 1"
And I set the field "Purpose" to "Purpose 1" And I set the field "Purpose" to "Purpose 1"
And I click on "Reset instances with custom values" "checkbox" When I click on "Reset instances with custom values" "checkbox"
When I press "Save changes" And I press "Save changes"
Then I should see "Category 1" And I should see "Category 1"
And I should see "Purpose 1" And I should see "Purpose 1"
And I navigate to "Users > Privacy and policies > Data registry" in site administration And I navigate to "Users > Privacy and policies > Data registry" in site administration
And I click on "Science and technology" "link" And I click on "Science and technology" "link"
And I wait until the page is ready And I wait until the page is ready
And the field "categoryid" matches value "Not set (use the default value)" 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 the field "purposeid" matches value "Not set (use the default value)"
And I should see "3 years" And I should see "3 years"
...@@ -94,6 +157,12 @@ Feature: Manage data registry defaults ...@@ -94,6 +157,12 @@ Feature: Manage data registry defaults
And the field "categoryid" matches value "Category 2" And the field "categoryid" matches value "Category 2"
And the field "purposeid" matches value "Purpose 2" And the field "purposeid" matches value "Purpose 2"
And I should see "5 years (after the course end date)" And I should see "5 years (after the course end date)"
And I click on "Activities and resources" "link"
And