MDL-61899 tool_dataprivacy: Refined patch fixing cibot complains
[moodle.git] / admin / tool / dataprivacy / classes / data_registry.php
blob57ca421f2e1ba3666c8373c42f87ca7bb5274322
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Data registry business logic methods. Mostly internal stuff.
20 * All methods should be considered part of the internal tool_dataprivacy API
21 * unless something different is specified.
23 * @package tool_dataprivacy
24 * @copyright 2018 David Monllao
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 namespace tool_dataprivacy;
30 use coding_exception;
31 use tool_dataprivacy\purpose;
32 use tool_dataprivacy\category;
33 use tool_dataprivacy\contextlevel;
34 use tool_dataprivacy\context_instance;
36 defined('MOODLE_INTERNAL') || die();
38 require_once($CFG->libdir . '/coursecatlib.php');
40 /**
41 * Data registry business logic methods. Mostly internal stuff.
43 * @copyright 2018 David Monllao
44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46 class data_registry {
48 /**
49 * @var array Inheritance between context levels.
51 private static $contextlevelinheritance = [
52 CONTEXT_USER => [CONTEXT_SYSTEM],
53 CONTEXT_COURSECAT => [CONTEXT_SYSTEM],
54 CONTEXT_COURSE => [CONTEXT_COURSECAT, CONTEXT_SYSTEM],
55 CONTEXT_MODULE => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
56 CONTEXT_BLOCK => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
59 /**
60 * Returns purpose and category var names from a context class name
62 * @access private
63 * @param string $classname
64 * @return string[]
66 public static function var_names_from_context($classname) {
67 return [
68 $classname . '_purpose',
69 $classname . '_category',
73 /**
74 * Returns the default purpose id and category id for the provided context level.
76 * The caller code is responsible of checking that $contextlevel is an integer.
78 * @access private
79 * @param int $contextlevel
80 * @return int|false[]
82 public static function get_defaults($contextlevel) {
84 $classname = \context_helper::get_class_for_level($contextlevel);
85 list($purposevar, $categoryvar) = self::var_names_from_context($classname);
87 $purposeid = get_config('tool_dataprivacy', $purposevar);
88 $categoryid = get_config('tool_dataprivacy', $categoryvar);
90 if (empty($purposeid)) {
91 $purposeid = false;
93 if (empty($categoryid)) {
94 $categoryid = false;
97 return [$purposeid, $categoryid];
101 * Are data registry defaults set?
103 * At least the system defaults need to be set.
105 * @access private
106 * @return bool
108 public static function defaults_set() {
109 list($purposeid, $categoryid) = self::get_defaults(CONTEXT_SYSTEM);
110 if (empty($purposeid) || empty($categoryid)) {
111 return false;
113 return true;
117 * Returns all site categories that are visible to the current user.
119 * @access private
120 * @return \coursecat[]
122 public static function get_site_categories() {
123 global $DB;
125 if (method_exists('\coursecat', 'get_all')) {
126 $categories = \coursecat::get_all(['returnhidden' => true]);
127 } else {
128 // Fallback (to be removed once this gets integrated into master).
129 $ids = $DB->get_fieldset_select('course_categories', 'id', '');
130 $categories = \coursecat::get_many($ids);
133 foreach ($categories as $key => $category) {
134 if (!$category->is_uservisible()) {
135 unset($categories[$key]);
138 return $categories;
142 * Returns the roles assigned to the provided level.
144 * Important to note that it returns course-level assigned roles
145 * if the provided context level is below course.
147 * @access private
148 * @param \context $context
149 * @return array
151 public static function get_subject_scope(\context $context) {
153 if ($contextcourse = $context->get_course_context(false)) {
154 // Below course level we only look at course-assigned roles.
155 $roles = get_user_roles($contextcourse, 0, false);
156 } else {
157 $roles = get_user_roles($context, 0, false);
160 return array_map(function($role) {
161 if ($role->name) {
162 return $role->name;
163 } else {
164 return $role->shortname;
166 }, $roles);
170 * Returns the effective value given a context instance
172 * @access private
173 * @param \context $context
174 * @param string $element 'category' or 'purpose'
175 * @param int|false $forcedvalue Use this value as if this was this context instance value.
176 * @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element
178 public static function get_effective_context_value(\context $context, $element, $forcedvalue=false) {
180 if ($element !== 'purpose' && $element !== 'category') {
181 throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
183 $fieldname = $element . 'id';
185 if ($forcedvalue === false) {
186 $instance = context_instance::get_record_by_contextid($context->id, false);
188 if (!$instance) {
189 // If the instance does not have a value defaults to not set, so we grab the context level default as its value.
190 $instancevalue = context_instance::NOTSET;
191 } else {
192 $instancevalue = $instance->get($fieldname);
194 } else {
195 $instancevalue = $forcedvalue;
198 // Not set.
199 if ($instancevalue == context_instance::NOTSET) {
201 // The effective value varies depending on the context level.
202 if ($context->contextlevel == CONTEXT_USER) {
203 // Use the context level value as we don't allow people to set specific instances values.
204 return self::get_effective_contextlevel_value($context->contextlevel, $element);
205 } else {
206 // Use the default context level value.
207 list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
208 $context->contextlevel
210 return self::get_element_instance($element, $$fieldname);
214 // Specific value for this context instance.
215 if ($instancevalue != context_instance::INHERIT) {
216 return self::get_element_instance($element, $instancevalue);
219 // This context is using inherited so let's return the parent effective value.
220 $parentcontext = $context->get_parent_context();
221 if (!$parentcontext) {
222 return false;
225 // The forced value should not be transmitted to parent contexts.
226 return self::get_effective_context_value($parentcontext, $element);
230 * Returns the effective value for a context level.
232 * Note that this is different from the effective default context level
233 * (see get_effective_default_contextlevel_purpose_and_category) as this is returning
234 * the value set in the data registry, not in the defaults page.
236 * @param int $contextlevel
237 * @param string $element 'category' or 'purpose'
238 * @param int $forcedvalue Use this value as if this was this context level purpose.
239 * @return \tool_dataprivacy\purpose|false
241 public static function get_effective_contextlevel_value($contextlevel, $element, $forcedvalue = false) {
243 if ($element !== 'purpose' && $element !== 'category') {
244 throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
246 $fieldname = $element . 'id';
248 if ($contextlevel != CONTEXT_SYSTEM && $contextlevel != CONTEXT_USER) {
249 throw new \coding_exception('Only context_system and context_user values can be retrieved, no other context levels ' .
250 'have a purpose or a category.');
253 if ($forcedvalue === false) {
254 $instance = contextlevel::get_record_by_contextlevel($contextlevel, false);
255 if (!$instance) {
256 // If the context level does not have a value defaults to not set, so we grab the context level default as
257 // its value.
258 $instancevalue = context_instance::NOTSET;
259 } else {
260 $instancevalue = $instance->get($fieldname);
262 } else {
263 $instancevalue = $forcedvalue;
266 // Not set -> Use the default context level value.
267 if ($instancevalue == context_instance::NOTSET) {
268 list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
269 return self::get_element_instance($element, $$fieldname);
272 // Specific value for this context instance.
273 if ($instancevalue != context_instance::INHERIT) {
274 return self::get_element_instance($element, $instancevalue);
277 if ($contextlevel == CONTEXT_SYSTEM) {
278 throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
281 // If we reach this point is that we are inheriting so get the parent context level and repeat.
282 $parentcontextlevel = reset(self::$contextlevelinheritance[$contextlevel]);
284 // Forced value are intentionally not passed as the force value should only affect the immediate context level.
285 return self::get_effective_contextlevel_value($parentcontextlevel, $element);
289 * Returns the effective default purpose and category for a context level.
291 * @param int $contextlevel
292 * @param int $forcedpurposevalue Use this value as if this was this context level purpose.
293 * @param int $forcedcategoryvalue Use this value as if this was this context level category.
294 * @return int[]
296 public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false, $forcedcategoryvalue = false) {
298 list($purposeid, $categoryid) = self::get_defaults($contextlevel);
300 // Honour forced values.
301 if ($forcedpurposevalue) {
302 $purposeid = $forcedpurposevalue;
304 if ($forcedcategoryvalue) {
305 $categoryid = $forcedcategoryvalue;
308 // Not set == INHERIT for defaults.
309 if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) {
310 $purposeid = false;
312 if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
313 $categoryid = false;
316 if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
317 foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
319 list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent);
320 // Not set == INHERIT for defaults.
321 if ($parentpurposeid == context_instance::INHERIT || $parentpurposeid == context_instance::NOTSET) {
322 $parentpurposeid = false;
324 if ($parentcategoryid == context_instance::INHERIT || $parentcategoryid == context_instance::NOTSET) {
325 $parentcategoryid = false;
328 if ($purposeid === false && $parentpurposeid) {
329 $purposeid = $parentpurposeid;
332 if ($categoryid === false && $parentcategoryid) {
333 $categoryid = $parentcategoryid;
338 // They may still be false, but we return anyway.
339 return [$purposeid, $categoryid];
343 * Returns an instance of the provided element.
345 * @throws \coding_exception
346 * @param string $element The element name 'purpose' or 'category'
347 * @param int $id The element id
348 * @return \core\persistent
350 private static function get_element_instance($element, $id) {
352 if ($element !== 'purpose' && $element !== 'category') {
353 throw new coding_exception('No other elements than purpose and category are allowed');
356 $classname = '\tool_dataprivacy\\' . $element;
357 return new $classname($id);