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.
   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   * Privacy Subsystem implementation for mod_choice.
  19   *
  20   * @package    mod_choice
  21   * @category   privacy
  22   * @copyright  2018 Jun Pataleta
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace mod_choice\privacy;
  27  
  28  use core_privacy\local\metadata\collection;
  29  use core_privacy\local\request\approved_contextlist;
  30  use core_privacy\local\request\approved_userlist;
  31  use core_privacy\local\request\contextlist;
  32  use core_privacy\local\request\deletion_criteria;
  33  use core_privacy\local\request\helper;
  34  use core_privacy\local\request\userlist;
  35  use core_privacy\local\request\writer;
  36  
  37  defined('MOODLE_INTERNAL') || die();
  38  
  39  /**
  40   * Implementation of the privacy subsystem plugin provider for the choice activity module.
  41   *
  42   * @copyright  2018 Jun Pataleta
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class provider implements
  46          // This plugin stores personal data.
  47          \core_privacy\local\metadata\provider,
  48  
  49          // This plugin is a core_user_data_provider.
  50          \core_privacy\local\request\plugin\provider,
  51  
  52          // This plugin is capable of determining which users have data within it.
  53          \core_privacy\local\request\core_userlist_provider {
  54      /**
  55       * Return the fields which contain personal data.
  56       *
  57       * @param collection $items a reference to the collection to use to store the metadata.
  58       * @return collection the updated collection of metadata items.
  59       */
  60      public static function get_metadata(collection $items) : collection {
  61          $items->add_database_table(
  62              'choice_answers',
  63              [
  64                  'choiceid' => 'privacy:metadata:choice_answers:choiceid',
  65                  'optionid' => 'privacy:metadata:choice_answers:optionid',
  66                  'userid' => 'privacy:metadata:choice_answers:userid',
  67                  'timemodified' => 'privacy:metadata:choice_answers:timemodified',
  68              ],
  69              'privacy:metadata:choice_answers'
  70          );
  71  
  72          return $items;
  73      }
  74  
  75      /**
  76       * Get the list of contexts that contain user information for the specified user.
  77       *
  78       * @param int $userid the userid.
  79       * @return contextlist the list of contexts containing user info for the user.
  80       */
  81      public static function get_contexts_for_userid(int $userid) : contextlist {
  82          // Fetch all choice answers.
  83          $sql = "SELECT c.id
  84                    FROM {context} c
  85              INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
  86              INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
  87              INNER JOIN {choice} ch ON ch.id = cm.instance
  88              INNER JOIN {choice_options} co ON co.choiceid = ch.id
  89              INNER JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id
  90                   WHERE ca.userid = :userid";
  91  
  92          $params = [
  93              'modname'       => 'choice',
  94              'contextlevel'  => CONTEXT_MODULE,
  95              'userid'        => $userid,
  96          ];
  97          $contextlist = new contextlist();
  98          $contextlist->add_from_sql($sql, $params);
  99  
 100          return $contextlist;
 101      }
 102  
 103      /**
 104       * Get the list of users who have data within a context.
 105       *
 106       * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
 107       */
 108      public static function get_users_in_context(userlist $userlist) {
 109          $context = $userlist->get_context();
 110  
 111          if (!$context instanceof \context_module) {
 112              return;
 113          }
 114  
 115          // Fetch all choice answers.
 116          $sql = "SELECT ca.userid
 117                    FROM {course_modules} cm
 118                    JOIN {modules} m ON m.id = cm.module AND m.name = :modname
 119                    JOIN {choice} ch ON ch.id = cm.instance
 120                    JOIN {choice_options} co ON co.choiceid = ch.id
 121                    JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id
 122                   WHERE cm.id = :cmid";
 123  
 124          $params = [
 125              'cmid'      => $context->instanceid,
 126              'modname'   => 'choice',
 127          ];
 128  
 129          $userlist->add_from_sql('userid', $sql, $params);
 130      }
 131  
 132      /**
 133       * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
 134       *
 135       * @param approved_contextlist $contextlist a list of contexts approved for export.
 136       */
 137      public static function export_user_data(approved_contextlist $contextlist) {
 138          global $DB;
 139  
 140          if (empty($contextlist->count())) {
 141              return;
 142          }
 143  
 144          $user = $contextlist->get_user();
 145  
 146          list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
 147  
 148          $sql = "SELECT cm.id AS cmid,
 149                         co.text as answer,
 150                         ca.timemodified
 151                    FROM {context} c
 152              INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
 153              INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
 154              INNER JOIN {choice} ch ON ch.id = cm.instance
 155              INNER JOIN {choice_options} co ON co.choiceid = ch.id
 156              INNER JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id
 157                   WHERE c.id {$contextsql}
 158                         AND ca.userid = :userid
 159                ORDER BY cm.id";
 160  
 161          $params = ['modname' => 'choice', 'contextlevel' => CONTEXT_MODULE, 'userid' => $user->id] + $contextparams;
 162  
 163          // Reference to the choice activity seen in the last iteration of the loop. By comparing this with the current record, and
 164          // because we know the results are ordered, we know when we've moved to the answers for a new choice activity and therefore
 165          // when we can export the complete data for the last activity.
 166          $lastcmid = null;
 167  
 168          $choiceanswers = $DB->get_recordset_sql($sql, $params);
 169          foreach ($choiceanswers as $choiceanswer) {
 170              // If we've moved to a new choice, then write the last choice data and reinit the choice data array.
 171              if ($lastcmid != $choiceanswer->cmid) {
 172                  if (!empty($choicedata)) {
 173                      $context = \context_module::instance($lastcmid);
 174                      self::export_choice_data_for_user($choicedata, $context, $user);
 175                  }
 176                  $choicedata = [
 177                      'answer' => [],
 178                      'timemodified' => \core_privacy\local\request\transform::datetime($choiceanswer->timemodified),
 179                  ];
 180              }
 181              $choicedata['answer'][] = $choiceanswer->answer;
 182              $lastcmid = $choiceanswer->cmid;
 183          }
 184          $choiceanswers->close();
 185  
 186          // The data for the last activity won't have been written yet, so make sure to write it now!
 187          if (!empty($choicedata)) {
 188              $context = \context_module::instance($lastcmid);
 189              self::export_choice_data_for_user($choicedata, $context, $user);
 190          }
 191      }
 192  
 193      /**
 194       * Export the supplied personal data for a single choice activity, along with any generic data or area files.
 195       *
 196       * @param array $choicedata the personal data to export for the choice.
 197       * @param \context_module $context the context of the choice.
 198       * @param \stdClass $user the user record
 199       */
 200      protected static function export_choice_data_for_user(array $choicedata, \context_module $context, \stdClass $user) {
 201          // Fetch the generic module data for the choice.
 202          $contextdata = helper::get_context_data($context, $user);
 203  
 204          // Merge with choice data and write it.
 205          $contextdata = (object)array_merge((array)$contextdata, $choicedata);
 206          writer::with_context($context)->export_data([], $contextdata);
 207  
 208          // Write generic module intro files.
 209          helper::export_context_files($context, $user);
 210      }
 211  
 212      /**
 213       * Delete all data for all users in the specified context.
 214       *
 215       * @param \context $context the context to delete in.
 216       */
 217      public static function delete_data_for_all_users_in_context(\context $context) {
 218          global $DB;
 219  
 220          if (!$context instanceof \context_module) {
 221              return;
 222          }
 223  
 224          if ($cm = get_coursemodule_from_id('choice', $context->instanceid)) {
 225              $DB->delete_records('choice_answers', ['choiceid' => $cm->instance]);
 226          }
 227      }
 228  
 229      /**
 230       * Delete all user data for the specified user, in the specified contexts.
 231       *
 232       * @param approved_contextlist $contextlist a list of contexts approved for deletion.
 233       */
 234      public static function delete_data_for_user(approved_contextlist $contextlist) {
 235          global $DB;
 236  
 237          if (empty($contextlist->count())) {
 238              return;
 239          }
 240  
 241          $userid = $contextlist->get_user()->id;
 242          foreach ($contextlist->get_contexts() as $context) {
 243  
 244              if (!$context instanceof \context_module) {
 245                  continue;
 246              }
 247              $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid]);
 248              if (!$instanceid) {
 249                  continue;
 250              }
 251              $DB->delete_records('choice_answers', ['choiceid' => $instanceid, 'userid' => $userid]);
 252          }
 253      }
 254  
 255      /**
 256       * Delete multiple users within a single context.
 257       *
 258       * @param   approved_userlist       $userlist The approved context and user information to delete information for.
 259       */
 260      public static function delete_data_for_users(approved_userlist $userlist) {
 261          global $DB;
 262  
 263          $context = $userlist->get_context();
 264  
 265          if (!$context instanceof \context_module) {
 266              return;
 267          }
 268  
 269          $cm = get_coursemodule_from_id('choice', $context->instanceid);
 270  
 271          if (!$cm) {
 272              // Only choice module will be handled.
 273              return;
 274          }
 275  
 276          $userids = $userlist->get_userids();
 277          list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
 278  
 279          $select = "choiceid = :choiceid AND userid $usersql";
 280          $params = ['choiceid' => $cm->instance] + $userparams;
 281          $DB->delete_records_select('choice_answers', $select, $params);
 282      }
 283  }