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