Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   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 privacy plugin library
  19   * @package   tool_dataprivacy
  20   * @copyright 2018 onwards Jun Pataleta
  21   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  use core_user\output\myprofile\tree;
  25  use tool_dataprivacy\form\exportfilter_form;
  26  
  27  /**
  28   * Add nodes to myprofile page.
  29   *
  30   * @param tree $tree Tree object
  31   * @param stdClass $user User object
  32   * @param bool $iscurrentuser
  33   * @param stdClass $course Course object
  34   * @return bool
  35   * @throws coding_exception
  36   * @throws dml_exception
  37   * @throws moodle_exception
  38   */
  39  function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser, $course) {
  40      global $PAGE, $USER;
  41  
  42      // Get the Privacy and policies category.
  43      if (!array_key_exists('privacyandpolicies', $tree->__get('categories'))) {
  44          // Create the category.
  45          $categoryname = get_string('privacyandpolicies', 'admin');
  46          $category = new core_user\output\myprofile\category('privacyandpolicies', $categoryname, 'contact');
  47          $tree->add_category($category);
  48      } else {
  49          // Get the existing category.
  50          $category = $tree->__get('categories')['privacyandpolicies'];
  51      }
  52  
  53      // Contact data protection officer link.
  54      if (\tool_dataprivacy\api::can_contact_dpo() && $iscurrentuser) {
  55          $renderer = $PAGE->get_renderer('tool_dataprivacy');
  56          $content = $renderer->render_contact_dpo_link();
  57          $node = new core_user\output\myprofile\node('privacyandpolicies', 'contactdpo', null, null, null, $content);
  58          $category->add_node($node);
  59  
  60          // Require our Javascript module to handle contact DPO interaction.
  61          $PAGE->requires->js_call_amd('tool_dataprivacy/contactdpo', 'init');
  62  
  63          $url = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php');
  64          $node = new core_user\output\myprofile\node('privacyandpolicies', 'datarequests',
  65              get_string('datarequests', 'tool_dataprivacy'), null, $url);
  66          $category->add_node($node);
  67  
  68          // Check if the user has an ongoing data export request.
  69          $hasexportrequest = \tool_dataprivacy\api::has_ongoing_request($user->id, \tool_dataprivacy\api::DATAREQUEST_TYPE_EXPORT);
  70          // Show data export link only if the user doesn't have an ongoing data export request and has permission
  71          // to download own data.
  72          if (!$hasexportrequest && \tool_dataprivacy\api::can_create_data_download_request_for_self()) {
  73              $exportparams = ['type' => \tool_dataprivacy\api::DATAREQUEST_TYPE_EXPORT];
  74              $exporturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', $exportparams);
  75              $exportnode = new core_user\output\myprofile\node('privacyandpolicies', 'requestdataexport',
  76                  get_string('requesttypeexport', 'tool_dataprivacy'), null, $exporturl);
  77              $category->add_node($exportnode);
  78          }
  79  
  80          // Check if the user has an ongoing data deletion request.
  81          $hasdeleterequest = \tool_dataprivacy\api::has_ongoing_request($user->id, \tool_dataprivacy\api::DATAREQUEST_TYPE_DELETE);
  82          // Show data deletion link only if the user doesn't have an ongoing data deletion request and has permission
  83          // to create data deletion request.
  84          if (!$hasdeleterequest && \tool_dataprivacy\api::can_create_data_deletion_request_for_self()) {
  85              $deleteparams = ['type' => \tool_dataprivacy\api::DATAREQUEST_TYPE_DELETE];
  86              $deleteurl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', $deleteparams);
  87              $deletenode = new core_user\output\myprofile\node('privacyandpolicies', 'requestdatadeletion',
  88                  get_string('deletemyaccount', 'tool_dataprivacy'), null, $deleteurl);
  89              $category->add_node($deletenode);
  90          }
  91      }
  92  
  93      // A returned 0 means that the setting was set and disabled, false means that there is no value for the provided setting.
  94      $showsummary = get_config('tool_dataprivacy', 'showdataretentionsummary');
  95      if ($showsummary === false) {
  96          // This means that no value is stored in db. We use the default value in this case.
  97          $showsummary = true;
  98      }
  99  
 100      if ($showsummary && $iscurrentuser) {
 101          $summaryurl = new moodle_url('/admin/tool/dataprivacy/summary.php');
 102          $summarynode = new core_user\output\myprofile\node('privacyandpolicies', 'retentionsummary',
 103              get_string('dataretentionsummary', 'tool_dataprivacy'), null, $summaryurl);
 104          $category->add_node($summarynode);
 105      }
 106  
 107      // Add the Privacy category to the tree if it's not empty and it doesn't exist.
 108      $nodes = $category->nodes;
 109      if (!empty($nodes)) {
 110          if (!array_key_exists('privacyandpolicies', $tree->__get('categories'))) {
 111              $tree->add_category($category);
 112          }
 113          return true;
 114      }
 115  
 116      return false;
 117  }
 118  
 119  /**
 120   * Callback to add footer elements.
 121   *
 122   * @return string HTML footer content
 123   */
 124  function tool_dataprivacy_standard_footer_html() {
 125      $output = '';
 126  
 127      // A returned 0 means that the setting was set and disabled, false means that there is no value for the provided setting.
 128      $showsummary = get_config('tool_dataprivacy', 'showdataretentionsummary');
 129      if ($showsummary === false) {
 130          // This means that no value is stored in db. We use the default value in this case.
 131          $showsummary = true;
 132      }
 133  
 134      if ($showsummary) {
 135          $url = new moodle_url('/admin/tool/dataprivacy/summary.php');
 136          $output = html_writer::link($url, get_string('dataretentionsummary', 'tool_dataprivacy'));
 137          $output = html_writer::div($output, 'tool_dataprivacy');
 138      }
 139      return $output;
 140  }
 141  
 142  /**
 143   * Fragment to add a new purpose.
 144   *
 145   * @param array $args The fragment arguments.
 146   * @return string The rendered mform fragment.
 147   */
 148  function tool_dataprivacy_output_fragment_addpurpose_form($args) {
 149  
 150      $formdata = [];
 151      if (!empty($args['jsonformdata'])) {
 152          $serialiseddata = json_decode($args['jsonformdata']);
 153          parse_str($serialiseddata, $formdata);
 154      }
 155  
 156      $persistent = new \tool_dataprivacy\purpose();
 157      $mform = new \tool_dataprivacy\form\purpose(null, ['persistent' => $persistent],
 158          'post', '', null, true, $formdata);
 159  
 160      if (!empty($args['jsonformdata'])) {
 161          // Show errors if data was received.
 162          $mform->is_validated();
 163      }
 164  
 165      return $mform->render();
 166  }
 167  
 168  /**
 169   * Fragment to add a new category.
 170   *
 171   * @param array $args The fragment arguments.
 172   * @return string The rendered mform fragment.
 173   */
 174  function tool_dataprivacy_output_fragment_addcategory_form($args) {
 175  
 176      $formdata = [];
 177      if (!empty($args['jsonformdata'])) {
 178          $serialiseddata = json_decode($args['jsonformdata']);
 179          parse_str($serialiseddata, $formdata);
 180      }
 181  
 182      $persistent = new \tool_dataprivacy\category();
 183      $mform = new \tool_dataprivacy\form\category(null, ['persistent' => $persistent],
 184          'post', '', null, true, $formdata);
 185  
 186      if (!empty($args['jsonformdata'])) {
 187          // Show errors if data was received.
 188          $mform->is_validated();
 189      }
 190  
 191      return $mform->render();
 192  }
 193  
 194  /**
 195   * Fragment to edit a context purpose and category.
 196   *
 197   * @param array $args The fragment arguments.
 198   * @return string The rendered mform fragment.
 199   */
 200  function tool_dataprivacy_output_fragment_context_form($args) {
 201      global $PAGE;
 202  
 203      $contextid = $args[0];
 204  
 205      $context = \context_helper::instance_by_id($contextid);
 206      $customdata = \tool_dataprivacy\form\context_instance::get_context_instance_customdata($context);
 207  
 208      if (!empty($customdata['purposeretentionperiods'])) {
 209          $PAGE->requires->js_call_amd('tool_dataprivacy/effective_retention_period', 'init',
 210              [$customdata['purposeretentionperiods']]);
 211      }
 212      $mform = new \tool_dataprivacy\form\context_instance(null, $customdata);
 213      return $mform->render();
 214  }
 215  
 216  /**
 217   * Fragment to edit a contextlevel purpose and category.
 218   *
 219   * @param array $args The fragment arguments.
 220   * @return string The rendered mform fragment.
 221   */
 222  function tool_dataprivacy_output_fragment_contextlevel_form($args) {
 223      global $PAGE;
 224  
 225      $contextlevel = $args[0];
 226      $customdata = \tool_dataprivacy\form\contextlevel::get_contextlevel_customdata($contextlevel);
 227  
 228      if (!empty($customdata['purposeretentionperiods'])) {
 229          $PAGE->requires->js_call_amd('tool_dataprivacy/effective_retention_period', 'init',
 230              [$customdata['purposeretentionperiods']]);
 231      }
 232  
 233      $mform = new \tool_dataprivacy\form\contextlevel(null, $customdata);
 234      return $mform->render();
 235  }
 236  
 237  /**
 238   * Serves any files associated with the data privacy settings.
 239   *
 240   * @param stdClass $course Course object
 241   * @param stdClass $cm Course module object
 242   * @param context $context Context
 243   * @param string $filearea File area for data privacy
 244   * @param array $args Arguments
 245   * @param bool $forcedownload If we are forcing the download
 246   * @param array $options More options
 247   * @return bool Returns false if we don't find a file.
 248   */
 249  function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
 250      if ($context->contextlevel == CONTEXT_USER) {
 251          // Make sure the user is logged in.
 252          require_login(null, false);
 253  
 254          // Get the data request ID. This should be the first element of the $args array.
 255          $itemid = $args[0];
 256          // Fetch the data request object. An invalid ID will throw an exception.
 257          $datarequest = new \tool_dataprivacy\data_request($itemid);
 258  
 259          // Check if user is allowed to download it.
 260          if (!\tool_dataprivacy\api::can_download_data_request_for_user($context->instanceid, $datarequest->get('requestedby'))) {
 261              return false;
 262          }
 263  
 264          // Make the file unavailable if it has expired.
 265          if (\tool_dataprivacy\data_request::is_expired($datarequest)) {
 266              send_file_not_found();
 267          }
 268  
 269          // All good. Serve the exported data.
 270          $fs = get_file_storage();
 271          $relativepath = implode('/', $args);
 272          $fullpath = "/$context->id/tool_dataprivacy/$filearea/$relativepath";
 273          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
 274              return false;
 275          }
 276          send_stored_file($file, 0, 0, $forcedownload, $options);
 277      } else {
 278          send_file_not_found();
 279      }
 280  }
 281  
 282  /**
 283   * Fragment to add a select course.
 284   *
 285   * @param array $args The fragment arguments.
 286   * @return string The rendered mform fragment.
 287   */
 288  function tool_dataprivacy_output_fragment_selectcourses_form(array $args): string {
 289      $args = (object)$args;
 290  
 291      $context = context_system::instance();
 292      require_capability('tool/dataprivacy:managedatarequests', $context);
 293  
 294      if (!empty($args->jsonformdata)) {
 295          $serialiseddata = json_decode($args->jsonformdata);
 296      }
 297  
 298      $mform = new exportfilter_form(null, ['requestid' => $serialiseddata->requestid]);
 299  
 300      return $mform->render();
 301  }