Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

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