MDL-61637 tool_dataprivacy: Context-based fetching
[moodle.git] / admin / tool / dataprivacy / classes / output / data_registry_page.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 renderable.
19 *
20 * @package tool_dataprivacy
21 * @copyright 2018 David Monllao
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace tool_dataprivacy\output;
25defined('MOODLE_INTERNAL') || die();
26
27use renderable;
28use renderer_base;
29use stdClass;
30use templatable;
31use tool_dataprivacy\data_registry;
32
33require_once($CFG->libdir . '/coursecatlib.php');
34require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
35require_once($CFG->libdir . '/blocklib.php');
36
37/**
38 * Class containing the data registry renderable
39 *
40 * @copyright 2018 David Monllao
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 */
43class data_registry_page implements renderable, templatable {
44
45 /**
46 * @var int
47 */
48 private $defaultcontextlevel;
49
50 /**
51 * @var int
52 */
53 private $defaultcontextid;
54
55 /**
56 * Constructor.
57 *
58 * @param int $defaultcontextlevel
59 * @param int $defaultcontextid
60 * @return null
61 */
62 public function __construct($defaultcontextlevel = false, $defaultcontextid = false) {
63 $this->defaultcontextlevel = $defaultcontextlevel;
64 $this->defaultcontextid = $defaultcontextid;
65 }
66
67 /**
68 * Export this data so it can be used as the context for a mustache template.
69 *
70 * @param renderer_base $output
71 * @return stdClass
72 */
73 public function export_for_template(renderer_base $output) {
74 global $PAGE;
75
76 $params = [\context_system::instance()->id, $this->defaultcontextlevel, $this->defaultcontextid];
77 $PAGE->requires->js_call_amd('tool_dataprivacy/data_registry', 'init', $params);
78
79 $data = new stdClass();
80 $defaultsbutton = new \action_link(
81 new \moodle_url('/admin/tool/dataprivacy/defaults.php'),
82 get_string('setdefaults', 'tool_dataprivacy'),
83 null,
84 ['class' => 'btn btn-default']
85 );
86 $data->defaultsbutton = $defaultsbutton->export_for_template($output);
87
88 $actionmenu = new \action_menu();
89 $actionmenu->set_menu_trigger(get_string('edit'), 'btn btn-default');
90 $actionmenu->set_owner_selector('dataregistry-actions');
91 $actionmenu->set_alignment(\action_menu::TL, \action_menu::BL);
92
93 $url = new \moodle_url('/admin/tool/dataprivacy/categories.php');
94 $categories = new \action_menu_link_secondary($url, null, get_string('categories', 'tool_dataprivacy'));
95 $actionmenu->add($categories);
96
97 $url = new \moodle_url('/admin/tool/dataprivacy/purposes.php');
98 $purposes = new \action_menu_link_secondary($url, null, get_string('purposes', 'tool_dataprivacy'));
99 $actionmenu->add($purposes);
100
101 $data->actions = $actionmenu->export_for_template($output);
102
103 if (!data_registry::defaults_set()) {
104 $data->nosystemdefaults = (object)[
105 'message' => get_string('nosystemdefaults', 'tool_dataprivacy'),
106 'announce' => 1
107 ];
108 }
109
110 $data->tree = $this->get_default_tree_structure();
111
112 return $data;
113 }
114
115 /**
116 * Returns the tree default structure.
117 *
118 * @return array
119 */
120 private function get_default_tree_structure() {
121
122 $frontpage = \context_course::instance(SITEID);
123
124 $categorybranches = $this->get_all_category_branches();
125
126 $elements = [
127 'text' => get_string('contextlevelname' . CONTEXT_SYSTEM, 'tool_dataprivacy'),
128 'contextlevel' => CONTEXT_SYSTEM,
129 'branches' => [
130 [
131 'text' => get_string('user'),
132 'contextlevel' => CONTEXT_USER,
133 ], [
134 'text' => get_string('categories', 'tool_dataprivacy'),
135 'branches' => $categorybranches,
136 'expandelement' => 'category',
137 ], [
138 'text' => get_string('frontpagecourse', 'tool_dataprivacy'),
139 'contextid' => $frontpage->id,
140 'branches' => [
141 [
142 'text' => get_string('activitiesandresources', 'tool_dataprivacy'),
143 'expandcontextid' => $frontpage->id,
144 'expandelement' => 'module',
145 'expanded' => 0,
146 ], [
147 'text' => get_string('blocks'),
148 'expandcontextid' => $frontpage->id,
149 'expandelement' => 'block',
150 'expanded' => 0,
151 ],
152 ]
153 ]
154 ]
155 ];
156
157 // Returned as an array to follow a common array format.
158 return [self::complete($elements, $this->defaultcontextlevel, $this->defaultcontextid)];
159 }
160
161 /**
162 * Returns the hierarchy of system course categories.
163 *
164 * @return array
165 */
166 private function get_all_category_branches() {
167
168 $categories = data_registry::get_site_categories();
169
170 $categoriesbranch = [];
171 while (count($categories) > 0) {
172 foreach ($categories as $key => $category) {
173
174 $context = \context_coursecat::instance($category->id);
175 $newnode = [
176 'text' => shorten_text(format_string($category->name, true, ['context' => $context])),
177 'categoryid' => $category->id,
178 'contextid' => $context->id,
179 ];
180 if ($category->coursecount > 0) {
181 $newnode['branches'] = [
182 [
183 'text' => get_string('courses'),
184 'expandcontextid' => $context->id,
185 'expandelement' => 'course',
186 'expanded' => 0,
187 ]
188 ];
189 }
190
191 $added = false;
192 if ($category->parent == 0) {
193 // New categories root-level node.
194 $categoriesbranch[] = $newnode;
195 $added = true;
196
197 } else {
198 // Add the new node under the appropriate parent.
199 if ($this->add_to_parent_category_branch($category, $newnode, $categoriesbranch)) {
200 $added = true;
201 }
202 }
203
204 if ($added) {
205 unset($categories[$key]);
206 }
207 }
208 }
209
210 return $categoriesbranch;
211 }
212
213 /**
214 * Gets the courses branch for the provided category.
215 *
216 * @param \context $catcontext
217 * @return array
218 */
219 public static function get_courses_branch(\context $catcontext) {
220
221 if ($catcontext->contextlevel !== CONTEXT_COURSECAT) {
222 throw new \coding_exception('A course category context should be provided');
223 }
224
225 $coursecat = \coursecat::get($catcontext->instanceid);
226 $courses = $coursecat->get_courses();
227
228 $branches = [];
229
230 foreach ($courses as $course) {
231
232 $coursecontext = \context_course::instance($course->id);
233
234 if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
235 continue;
236 }
237
238 $coursenode = [
239 'text' => shorten_text(format_string($course->shortname, true, ['context' => $coursecontext])),
240 'contextid' => $coursecontext->id,
241 'branches' => [
242 [
243 'text' => get_string('activitiesandresources', 'tool_dataprivacy'),
244 'expandcontextid' => $coursecontext->id,
245 'expandelement' => 'module',
246 'expanded' => 0,
247 ], [
248 'text' => get_string('blocks'),
249 'expandcontextid' => $coursecontext->id,
250 'expandelement' => 'block',
251 'expanded' => 0,
252 ],
253 ]
254 ];
255 $branches[] = self::complete($coursenode);
256 }
257
258 return $branches;
259 }
260
261 /**
262 * Gets the modules branch for the provided course.
263 *
264 * @param \context $coursecontext
265 * @return array
266 */
267 public static function get_modules_branch(\context $coursecontext) {
268
269 if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
270 throw new \coding_exception('A course context should be provided');
271 }
272
273 $branches = [];
274
275 // Using the current user.
276 $modinfo = get_fast_modinfo($coursecontext->instanceid);
277 foreach ($modinfo->get_instances() as $moduletype => $instances) {
278 foreach ($instances as $cm) {
279
280 if (!$cm->uservisible) {
281 continue;
282 }
283
284 $a = (object)[
285 'instancename' => shorten_text($cm->get_formatted_name()),
286 'modulename' => get_string('pluginname', 'mod_' . $moduletype),
287 ];
288
289 $text = get_string('moduleinstancename', 'tool_dataprivacy', $a);
290 $branches[] = self::complete([
291 'text' => $text,
292 'contextid' => $cm->context->id,
293 ]);
294 }
295 }
296
297 return $branches;
298 }
299
300 /**
301 * Gets the blocks branch for the provided course.
302 *
303 * @param \context $coursecontext
304 * @return null
305 */
306 public static function get_blocks_branch(\context $coursecontext) {
307 global $DB;
308
309 if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
310 throw new \coding_exception('A course context should be provided');
311 }
312
313 $branches = [];
314
c459409c
DM
315 $children = $coursecontext->get_child_contexts();
316 foreach ($children as $childcontext) {
317
318 if ($childcontext->contextlevel !== CONTEXT_BLOCK) {
319 continue;
320 }
5efc1f9e 321
5efc1f9e 322 if (function_exists('block_instance_by_id')) {
c459409c 323 $blockinstance = block_instance_by_id($childcontext->instanceid);
5efc1f9e
DM
324 } else {
325 // TODO To be removed when MDL-61621 gets integrated.
c459409c 326 $blockinstance = $DB->get_record('block_instances', ['id' => $childcontext->instanceid]);
5efc1f9e
DM
327 $blockinstance = block_instance($blockinstance->blockname, $blockinstance);
328 }
c459409c 329 $displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $childcontext->id]));
5efc1f9e
DM
330 $branches[] = self::complete([
331 'text' => $displayname,
c459409c 332 'contextid' => $childcontext->id,
5efc1f9e 333 ]);
c459409c 334
5efc1f9e
DM
335 }
336
337 return $branches;
5efc1f9e
DM
338 }
339
340 /**
341 * Adds the provided category to the categories branch.
342 *
343 * @param stdClass $category
344 * @param array $newnode
345 * @param array $categoriesbranch
346 * @return bool
347 */
348 private function add_to_parent_category_branch($category, $newnode, &$categoriesbranch) {
349
350 foreach ($categoriesbranch as $key => $branch) {
351 if (!empty($branch['categoryid']) && $branch['categoryid'] == $category->parent) {
352 // It may be empty (if it does not contain courses and this is the first child cat).
353 if (!isset($categoriesbranch[$key]['branches'])) {
354 $categoriesbranch[$key]['branches'] = [];
355 }
356 $categoriesbranch[$key]['branches'][] = $newnode;
357 return true;
358 }
359 if (!empty($branch['branches'])) {
360 $parent = $this->add_to_parent_category_branch($category, $newnode, $categoriesbranch[$key]['branches']);
361 if ($parent) {
362 return true;
363 }
364 }
365 }
366
367 return false;
368 }
369
370 /**
371 * Completes tree nodes with default values.
372 *
373 * @param array $node
374 * @param int|false $currentcontextlevel
375 * @param int|false $currentcontextid
376 * @return array
377 */
378 private static function complete($node, $currentcontextlevel = false, $currentcontextid = false) {
379 if (!isset($node['active'])) {
380 if ($currentcontextlevel && !empty($node['contextlevel']) &&
381 $currentcontextlevel == $node['contextlevel'] &&
382 empty($currentcontextid)) {
383 // This is the active context level, we also checked that there
384 // is no default contextid set.
385 $node['active'] = true;
386 } else if ($currentcontextid && !empty($node['contextid']) &&
387 $currentcontextid == $node['contextid']) {
388 $node['active'] = true;
389 } else {
390 $node['active'] = null;
391 }
392 }
393
394 if (!isset($node['branches'])) {
395 $node['branches'] = [];
396 } else {
397 foreach ($node['branches'] as $key => $childnode) {
398 $node['branches'][$key] = self::complete($childnode, $currentcontextlevel, $currentcontextid);
399 }
400 }
401
402 if (!isset($node['expandelement'])) {
403 $node['expandelement'] = null;
404 }
405
406 if (!isset($node['expandcontextid'])) {
407 $node['expandcontextid'] = null;
408 }
409
410 if (!isset($node['contextid'])) {
411 $node['contextid'] = null;
412 }
413
414 if (!isset($node['contextlevel'])) {
415 $node['contextlevel'] = null;
416 }
417
418 if (!isset($node['expanded'])) {
419 if (!empty($node['branches'])) {
420 $node['expanded'] = 1;
421 } else {
422 $node['expanded'] = 0;
423 }
424 }
425 return $node;
426 }
427
428 /**
429 * From a list of purpose persistents to a list of id => name purposes.
430 *
431 * @param \tool_dataprivacy\purpose $purposes
432 * @param bool $includenotset
433 * @param bool $includeinherit
434 * @return string[]
435 */
436 public static function purpose_options($purposes, $includenotset = true, $includeinherit = true) {
437 $options = self::base_options($includenotset, $includeinherit);
438 foreach ($purposes as $purpose) {
439 $options[$purpose->get('id')] = $purpose->get('name');
440 }
441
442 return $options;
443 }
444
445 /**
446 * From a list of category persistents to a list of id => name categories.
447 *
448 * @param \tool_dataprivacy\category $categories
449 * @param bool $includenotset
450 * @param bool $includeinherit
451 * @return string[]
452 */
453 public static function category_options($categories, $includenotset = true, $includeinherit = true) {
454 $options = self::base_options($includenotset, $includeinherit);
455 foreach ($categories as $category) {
456 $options[$category->get('id')] = $category->get('name');
457 }
458
459 return $options;
460 }
461
462 /**
463 * Base not set and inherit options.
464 *
465 * @param bool $includenotset
466 * @param bool $includeinherit
467 * @return array
468 */
469 private static function base_options($includenotset = true, $includeinherit = true) {
470
471 $options = [];
472
473 if ($includenotset) {
474 $options[\tool_dataprivacy\context_instance::NOTSET] = get_string('notset', 'tool_dataprivacy');
475 }
476
477 if ($includeinherit) {
478 $options[\tool_dataprivacy\context_instance::INHERIT] = get_string('inherit', 'tool_dataprivacy');
479 }
480
481 return $options;
482 }
483}