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