MDL-61899 tool_dataprivacy: Subject access requests tool
[moodle.git] / admin / tool / dataprivacy / classes / data_registry.php
CommitLineData
5efc1f9e
DM
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.
13//
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/>.
16
17/**
18 * Data registry business logic methods. Mostly internal stuff.
19 *
20 * All methods should be considered part of the internal tool_dataprivacy API
21 * unless something different is specified.
22 *
23 * @package tool_dataprivacy
24 * @copyright 2018 David Monllao
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 */
27
28namespace tool_dataprivacy;
29
30use coding_exception;
31use tool_dataprivacy\purpose;
32use tool_dataprivacy\category;
33use tool_dataprivacy\contextlevel;
34use tool_dataprivacy\context_instance;
35
36defined('MOODLE_INTERNAL') || die();
37
38require_once($CFG->libdir . '/coursecatlib.php');
39
40/**
41 * Data registry business logic methods. Mostly internal stuff.
42 *
43 * @copyright 2018 David Monllao
44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45 */
46class data_registry {
47
48 /**
49 * @var array Inheritance between context levels.
50 */
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],
57 ];
58
59 /**
60 * Returns purpose and category var names from a context class name
61 *
62 * @access private
63 * @param string $classname
64 * @return string[]
65 */
66 public static function var_names_from_context($classname) {
67 return [
68 $classname . '_purpose',
69 $classname . '_category',
70 ];
71 }
72
73 /**
74 * Returns the default purpose id and category id for the provided context level.
75 *
76 * The caller code is responsible of checking that $contextlevel is an integer.
77 *
78 * @access private
79 * @param int $contextlevel
80 * @return int|false[]
81 */
82 public static function get_defaults($contextlevel) {
83
84 $classname = \context_helper::get_class_for_level($contextlevel);
85 list($purposevar, $categoryvar) = self::var_names_from_context($classname);
86
87 $purposeid = get_config('tool_dataprivacy', $purposevar);
88 $categoryid = get_config('tool_dataprivacy', $categoryvar);
89
90 if (empty($purposeid)) {
91 $purposeid = false;
92 }
93 if (empty($categoryid)) {
94 $categoryid = false;
95 }
96
97 return [$purposeid, $categoryid];
98 }
99
100 /**
101 * Are data registry defaults set?
102 *
103 * At least the system defaults need to be set.
104 *
105 * @access private
106 * @return bool
107 */
108 public static function defaults_set() {
109 list($purposeid, $categoryid) = self::get_defaults(CONTEXT_SYSTEM);
110 if (empty($purposeid) || empty($categoryid)) {
111 return false;
112 }
113 return true;
114 }
115
116 /**
117 * Returns all site categories that are visible to the current user.
118 *
119 * @access private
120 * @return \coursecat[]
121 */
122 public static function get_site_categories() {
123 global $DB;
124
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);
131 }
132
133 foreach ($categories as $key => $category) {
134 if (!$category->is_uservisible()) {
135 unset($categories[$key]);
136 }
137 }
138 return $categories;
139 }
140
141 /**
142 * Returns the roles assigned to the provided level.
143 *
144 * Important to note that it returns course-level assigned roles
145 * if the provided context level is below course.
146 *
147 * @access private
148 * @param \context $context
149 * @return array
150 */
151 public static function get_subject_scope(\context $context) {
152
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);
158 }
159
160 return array_map(function($role) {
161 if ($role->name) {
162 return $role->name;
163 } else {
164 return $role->shortname;
165 }
166 }, $roles);
167 }
168
169 /**
170 * Returns the effective value given a context instance
171 *
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
177 */
178 public static function get_effective_context_value(\context $context, $element, $forcedvalue=false) {
179
180 if ($element !== 'purpose' && $element !== 'category') {
181 throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
182 }
183 $fieldname = $element . 'id';
184
185 if ($forcedvalue === false) {
186 $instance = context_instance::get_record_by_contextid($context->id, false);
187
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);
193 }
194 } else {
195 $instancevalue = $forcedvalue;
196 }
197
198 // Not set.
199 if ($instancevalue == context_instance::NOTSET) {
200
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($context->contextlevel);
208 return self::get_element_instance($element, $$fieldname);
209 }
210 }
211
212 // Specific value for this context instance.
213 if ($instancevalue != context_instance::INHERIT) {
214 return self::get_element_instance($element, $instancevalue);
215 }
216
217 // This context is using inherited so let's return the parent effective value.
218 $parentcontext = $context->get_parent_context();
219 if (!$parentcontext) {
220 return false;
221 }
222
223 // The forced value should not be transmitted to parent contexts.
224 return self::get_effective_context_value($parentcontext, $element);
225 }
226
227 /**
228 * Returns the effective value for a context level.
229 *
230 * Note that this is different from the effective default context level
231 * (see get_effective_default_contextlevel_purpose_and_category) as this is returning
232 * the value set in the data registry, not in the defaults page.
233 *
234 * @param int $contextlevel
235 * @param string $element 'category' or 'purpose'
236 * @param int $forcedvalue Use this value as if this was this context level purpose.
237 * @return \tool_dataprivacy\purpose|false
238 */
239 public static function get_effective_contextlevel_value($contextlevel, $element, $forcedvalue = false) {
240
241 if ($element !== 'purpose' && $element !== 'category') {
242 throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
243 }
244 $fieldname = $element . 'id';
245
246 if ($contextlevel != CONTEXT_SYSTEM && $contextlevel != CONTEXT_USER) {
247 throw new \coding_exception('Only context_system and context_user values can be retrieved, no other context levels ' .
248 'have a purpose or a category.');
249 }
250
251 if ($forcedvalue === false) {
252 $instance = contextlevel::get_record_by_contextlevel($contextlevel, false);
253 if (!$instance) {
254 // If the context level does not have a value defaults to not set, so we grab the context level default as its value.
255 $instancevalue = context_instance::NOTSET;
256 } else {
257 $instancevalue = $instance->get($fieldname);
258 }
259 } else {
260 $instancevalue = $forcedvalue;
261 }
262
263 // Not set -> Use the default context level value.
264 if ($instancevalue == context_instance::NOTSET) {
265 list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
266 return self::get_element_instance($element, $$fieldname);
267 }
268
269 // Specific value for this context instance.
270 if ($instancevalue != context_instance::INHERIT) {
271 return self::get_element_instance($element, $instancevalue);
272 }
273
274 if ($contextlevel == CONTEXT_SYSTEM) {
275 throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
276 }
277
278 // If we reach this point is that we are inheriting so get the parent context level and repeat.
279 $parentcontextlevel = reset(self::$contextlevelinheritance[$contextlevel]);
280
281 // Forced value are intentionally not passed as the force value should only affect the immediate context level.
282 return self::get_effective_contextlevel_value($parentcontextlevel, $element);
283 }
284
285 /**
286 * Returns the effective default purpose and category for a context level.
287 *
288 * @param int $contextlevel
289 * @param int $forcedpurposevalue Use this value as if this was this context level purpose.
290 * @param int $forcedcategoryvalue Use this value as if this was this context level category.
291 * @return int[]
292 */
293 public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false, $forcedcategoryvalue = false) {
294
295 list($purposeid, $categoryid) = self::get_defaults($contextlevel);
296
297 // Honour forced values.
298 if ($forcedpurposevalue) {
299 $purposeid = $forcedpurposevalue;
300 }
301 if ($forcedcategoryvalue) {
302 $categoryid = $forcedcategoryvalue;
303 }
304
305 // Not set == INHERIT for defaults.
306 if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) {
307 $purposeid = false;
308 }
309 if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
310 $categoryid = false;
311 }
312
313 if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
314 foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
315
316 list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent);
317 // Not set == INHERIT for defaults.
318 if ($parentpurposeid == context_instance::INHERIT || $parentpurposeid == context_instance::NOTSET) {
319 $parentpurposeid = false;
320 }
321 if ($parentcategoryid == context_instance::INHERIT || $parentcategoryid == context_instance::NOTSET) {
322 $parentcategoryid = false;
323 }
324
325 if ($purposeid === false && $parentpurposeid) {
326 $purposeid = $parentpurposeid;
327 }
328
329 if ($categoryid === false && $parentcategoryid) {
330 $categoryid = $parentcategoryid;
331 }
332 }
333 }
334
335 // They may still be false, but we return anyway.
336 return [$purposeid, $categoryid];
337 }
338
339 /**
340 * Returns an instance of the provided element.
341 *
342 * @throws \coding_exception
343 * @param string $element The element name 'purpose' or 'category'
344 * @param int $id The element id
345 * @return \core\persistent
346 */
347 private static function get_element_instance($element, $id) {
348
349 if ($element !== 'purpose' && $element !== 'category') {
350 throw new coding_exception('No other elements than purpose and category are allowed');
351 }
352
353 $classname = '\tool_dataprivacy\\' . $element;
354 return new $classname($id);
355 }
356}