Merge branch 'MDL-61991-master' of https://github.com/lucaboesch/moodle
[moodle.git] / admin / tool / dataprivacy / classes / output / data_registry_page.php
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/>.
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  */
24 namespace tool_dataprivacy\output;
25 defined('MOODLE_INTERNAL') || die();
27 use renderable;
28 use renderer_base;
29 use stdClass;
30 use templatable;
31 use tool_dataprivacy\data_registry;
33 require_once($CFG->libdir . '/coursecatlib.php');
34 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
35 require_once($CFG->libdir . '/blocklib.php');
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  */
43 class data_registry_page implements renderable, templatable {
45     /**
46      * @var int
47      */
48     private $defaultcontextlevel;
50     /**
51      * @var int
52      */
53     private $defaultcontextid;
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     }
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;
76         $params = [\context_system::instance()->id, $this->defaultcontextlevel, $this->defaultcontextid];
77         $PAGE->requires->js_call_amd('tool_dataprivacy/data_registry', 'init', $params);
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);
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);
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);
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);
101         $data->actions = $actionmenu->export_for_template($output);
103         if (!data_registry::defaults_set()) {
104             $data->nosystemdefaults = (object)[
105                 'message' => get_string('nosystemdefaults', 'tool_dataprivacy'),
106                 'announce' => 1
107             ];
108         }
110         $data->tree = $this->get_default_tree_structure();
112         return $data;
113     }
115     /**
116      * Returns the tree default structure.
117      *
118      * @return array
119      */
120     private function get_default_tree_structure() {
122         $frontpage = \context_course::instance(SITEID);
124         $categorybranches = $this->get_all_category_branches();
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         ];
157         // Returned as an array to follow a common array format.
158         return [self::complete($elements, $this->defaultcontextlevel, $this->defaultcontextid)];
159     }
161     /**
162      * Returns the hierarchy of system course categories.
163      *
164      * @return array
165      */
166     private function get_all_category_branches() {
168         $categories = data_registry::get_site_categories();
170         $categoriesbranch = [];
171         while (count($categories) > 0) {
172             foreach ($categories as $key => $category) {
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                 }
191                 $added = false;
192                 if ($category->parent == 0) {
193                     // New categories root-level node.
194                     $categoriesbranch[] = $newnode;
195                     $added = true;
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                 }
204                 if ($added) {
205                     unset($categories[$key]);
206                 }
207             }
208         }
210         return $categoriesbranch;
211     }
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) {
221         if ($catcontext->contextlevel !== CONTEXT_COURSECAT) {
222             throw new \coding_exception('A course category context should be provided');
223         }
225         $coursecat = \coursecat::get($catcontext->instanceid);
226         $courses = $coursecat->get_courses();
228         $branches = [];
230         foreach ($courses as $course) {
232             $coursecontext = \context_course::instance($course->id);
234             if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
235                 continue;
236             }
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         }
258         return $branches;
259     }
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) {
269         if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
270             throw new \coding_exception('A course context should be provided');
271         }
273         $branches = [];
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) {
280                 if (!$cm->uservisible) {
281                     continue;
282                 }
284                 $a = (object)[
285                     'instancename' => shorten_text($cm->get_formatted_name()),
286                     'modulename' => get_string('pluginname', 'mod_' . $moduletype),
287                 ];
289                 $text = get_string('moduleinstancename', 'tool_dataprivacy', $a);
290                 $branches[] = self::complete([
291                     'text' => $text,
292                     'contextid' => $cm->context->id,
293                 ]);
294             }
295         }
297         return $branches;
298     }
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;
309         if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
310             throw new \coding_exception('A course context should be provided');
311         }
313         $branches = [];
315         $blockinstances = \core_block_external::get_course_blocks($coursecontext->instanceid);
316         if (empty($blockinstances['blocks'])) {
317             return $branches;
318         }
320         foreach ($blockinstances['blocks'] as $bi) {
321             if (function_exists('block_instance_by_id')) {
322                 $blockinstance = block_instance_by_id($bi['instanceid']);
323             } else {
324                 // TODO To be removed when MDL-61621 gets integrated.
325                 $blockinstance = $DB->get_record('block_instances', ['id' => $bi['instanceid']]);
326                 $blockinstance = block_instance($blockinstance->blockname, $blockinstance);
327             }
328             $blockcontext = \context_block::instance($bi['instanceid']);
329             $displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $blockcontext->id]));
330             $branches[] = self::complete([
331                 'text' => $displayname,
332                 'contextid' => $blockcontext->id,
333             ]);
334         }
336         return $branches;
338     }
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) {
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         }
367         return false;
368     }
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         }
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         }
402         if (!isset($node['expandelement'])) {
403             $node['expandelement'] = null;
404         }
406         if (!isset($node['expandcontextid'])) {
407             $node['expandcontextid'] = null;
408         }
410         if (!isset($node['contextid'])) {
411             $node['contextid'] = null;
412         }
414         if (!isset($node['contextlevel'])) {
415             $node['contextlevel'] = null;
416         }
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     }
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         }
442         return $options;
443     }
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         }
459         return $options;
460     }
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) {
471         $options = [];
473         if ($includenotset) {
474             $options[\tool_dataprivacy\context_instance::NOTSET] = get_string('notset', 'tool_dataprivacy');
475         }
477         if ($includeinherit) {
478             $options[\tool_dataprivacy\context_instance::INHERIT] = get_string('inherit', 'tool_dataprivacy');
479         }
481         return $options;
482     }