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 401 and 402] [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   * Privacy Subsystem implementation for core_enrol.
  18   *
  19   * @package    core_enrol
  20   * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  namespace core_enrol\privacy;
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  use core_privacy\local\metadata\collection;
  29  use core_privacy\local\request\approved_contextlist;
  30  use core_privacy\local\request\context;
  31  use core_privacy\local\request\contextlist;
  32  use core_privacy\local\request\transform;
  33  use core_privacy\local\request\writer;
  34  use core_privacy\local\request\userlist;
  35  use \core_privacy\local\request\approved_userlist;
  36  
  37  /**
  38   * Privacy Subsystem for core_enrol implementing metadata and plugin providers.
  39   *
  40   * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class provider implements
  44          \core_privacy\local\metadata\provider,
  45          \core_privacy\local\request\core_userlist_provider,
  46          \core_privacy\local\request\subsystem\provider {
  47      /**
  48       * Returns meta data about this system.
  49       *
  50       * @param   collection $collection The initialised collection to add items to.
  51       * @return  collection     A listing of user data stored through this system.
  52       */
  53      public static function get_metadata(collection $collection) : collection {
  54          $collection->add_database_table(
  55              'user_enrolments',
  56              [
  57                  'status' => 'privacy:metadata:user_enrolments:status',
  58                  'enrolid' => 'privacy:metadata:user_enrolments:enrolid',
  59                  'userid' => 'privacy:metadata:user_enrolments:userid',
  60                  'timestart' => 'privacy:metadata:user_enrolments:timestart',
  61                  'timeend' => 'privacy:metadata:user_enrolments:timeend',
  62                  'modifierid' => 'privacy:metadata:user_enrolments:modifierid',
  63                  'timecreated' => 'privacy:metadata:user_enrolments:timecreated',
  64                  'timemodified' => 'privacy:metadata:user_enrolments:timemodified'
  65              ],
  66              'privacy:metadata:user_enrolments:tableexplanation'
  67          );
  68  
  69          return $collection;
  70      }
  71      /**
  72       * Get the list of contexts that contain user information for the specified user.
  73       *
  74       * @param   int $userid The user to search.
  75       * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
  76       */
  77      public static function get_contexts_for_userid(int $userid) : contextlist {
  78          $sql = "SELECT ctx.id
  79                    FROM {user_enrolments} ue
  80                    JOIN {enrol} e
  81                      ON e.id = ue.enrolid
  82                     AND ue.userid = :userid
  83                    JOIN {context} ctx
  84                      ON ctx.instanceid = e.courseid
  85                     AND ctx.contextlevel = :contextlevel";
  86          $params = [
  87              'contextlevel' => CONTEXT_COURSE,
  88              'userid'       => $userid
  89          ];
  90          $contextlist = new contextlist();
  91          $contextlist->add_from_sql($sql, $params);
  92  
  93          return $contextlist;
  94      }
  95  
  96      /**
  97       * Get the list of users within a specific context.
  98       *
  99       * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
 100       */
 101      public static function get_users_in_context(userlist $userlist) {
 102          $context = $userlist->get_context();
 103  
 104          if (!$context instanceof \context_course) {
 105              return;
 106          }
 107  
 108          $sql = "SELECT ue.userid as userid
 109                    FROM {user_enrolments} ue
 110                    JOIN {enrol} e ON e.id = ue.enrolid
 111                   WHERE e.courseid = ?";
 112          $params = [$context->instanceid];
 113          $userlist->add_from_sql('userid', $sql, $params);
 114      }
 115  
 116      /**
 117       * Export all user data for the specified user, in the specified contexts.
 118       *
 119       * @param   approved_contextlist $contextlist The approved contexts to export information for.
 120       */
 121      public static function export_user_data(approved_contextlist $contextlist) {
 122          global $DB;
 123  
 124          if (empty($contextlist->count())) {
 125              return;
 126          }
 127          $userid = $contextlist->get_user()->id;
 128          list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
 129          $params = [
 130              'contextlevel' => CONTEXT_COURSE,
 131              'userid' => $userid
 132           ];
 133          $params += $inparams;
 134          $sql = "SELECT ue.id,
 135                         ue.status,
 136                         ue.timestart,
 137                         ue.timeend,
 138                         ue.timecreated,
 139                         ue.timemodified,
 140                         e.enrol,
 141                         ctx.id as contextid
 142                    FROM {user_enrolments} ue
 143                    JOIN {enrol} e
 144                      ON e.id = ue.enrolid
 145                     AND ue.userid = :userid
 146                    JOIN {context} ctx
 147                      ON ctx.instanceid = e.courseid
 148                     AND ctx.contextlevel = :contextlevel
 149                   WHERE ctx.id $insql
 150                   ORDER BY ctx.id, e.enrol";
 151          $data = [];
 152          $lastcontextid = null;
 153          $lastenrol = null;
 154          $path = [get_string('privacy:metadata:user_enrolments', 'core_enrol')];
 155          $flush = function($lastcontextid, $lastenrol, $data) use ($path) {
 156              $context = \context::instance_by_id($lastcontextid);
 157              writer::with_context($context)->export_related_data(
 158                  $path,
 159                  $lastenrol,
 160                  (object)$data
 161              );
 162          };
 163          $userenrolments = $DB->get_recordset_sql($sql, $params);
 164          foreach ($userenrolments as $userenrolment) {
 165              if (($lastcontextid && $lastcontextid != $userenrolment->contextid) ||
 166                      ($lastenrol && $lastenrol != $userenrolment->enrol)) {
 167                  $flush($lastcontextid, $lastenrol, $data);
 168                  $data = [];
 169              }
 170              $data[] = (object) [
 171                  'status' => $userenrolment->status,
 172                  'timecreated' => transform::datetime($userenrolment->timecreated),
 173                  'timemodified' => transform::datetime($userenrolment->timemodified),
 174                  'timestart' => transform::datetime($userenrolment->timestart),
 175                  'timeend' => transform::datetime($userenrolment->timeend)
 176              ];
 177              $lastcontextid = $userenrolment->contextid;
 178              $lastenrol = $userenrolment->enrol;
 179          }
 180          if (!empty($data)) {
 181              $flush($lastcontextid, $lastenrol, $data);
 182          }
 183          $userenrolments->close();
 184      }
 185      /**
 186       * Delete all data for all users in the specified context.
 187       *
 188       * @param   context $context The specific context to delete data for.
 189       */
 190      public static function delete_data_for_all_users_in_context(\context $context) {
 191          global $DB;
 192  
 193          // Sanity check that context is at the User context level.
 194          if ($context->contextlevel == CONTEXT_COURSE) {
 195              $sql = "SELECT ue.id
 196                        FROM {user_enrolments} ue
 197                        JOIN {enrol} e ON e.id = ue.enrolid
 198                       WHERE e.courseid = :courseid";
 199              $params = ['courseid' => $context->instanceid];
 200              $enrolsids = $DB->get_fieldset_sql($sql, $params);
 201              if (!empty($enrolsids)) {
 202                  list($insql, $inparams) = $DB->get_in_or_equal($enrolsids, SQL_PARAMS_NAMED);
 203                  static::delete_user_data($insql, $inparams);
 204              }
 205          }
 206      }
 207  
 208      /**
 209       * Delete multiple users within a single context.
 210       *
 211       * @param approved_userlist $userlist The approved context and user information to delete information for.
 212       */
 213      public static function delete_data_for_users(approved_userlist $userlist) {
 214          global $DB;
 215  
 216          $context = $userlist->get_context();
 217  
 218          if ($context instanceof \context_course) {
 219              list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 220  
 221              $sql = "SELECT ue.id
 222                        FROM {user_enrolments} ue
 223                        JOIN {enrol} e ON e.id = ue.enrolid
 224                       WHERE e.courseid = :courseid
 225                             AND ue.userid {$usersql}";
 226  
 227              $params = ['courseid' => $context->instanceid] + $userparams;
 228              $enrolsids = $DB->get_fieldset_sql($sql, $params);
 229  
 230              if (!empty($enrolsids)) {
 231                  list($insql, $inparams) = $DB->get_in_or_equal($enrolsids, SQL_PARAMS_NAMED);
 232                  static::delete_user_data($insql, $inparams);
 233              }
 234          }
 235      }
 236  
 237      /**
 238       * Delete all user data for the specified user, in the specified contexts.
 239       *
 240       * @param   approved_contextlist $contextlist The approved contexts and user information to delete information for.
 241       */
 242      public static function delete_data_for_user(approved_contextlist $contextlist) {
 243          global $DB;
 244  
 245          if (empty($contextlist->count())) {
 246              return;
 247          }
 248          $userid = $contextlist->get_user()->id;
 249          list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
 250          $params = [
 251              'contextlevel' => CONTEXT_COURSE,
 252              'userid' => $userid
 253           ];
 254          $params += $inparams;
 255          $sql = "SELECT ue.id
 256                    FROM {user_enrolments} ue
 257                    JOIN {enrol} e
 258                      ON e.id = ue.enrolid
 259                     AND ue.userid = :userid
 260                    JOIN {context} ctx
 261                      ON ctx.instanceid = e.courseid
 262                     AND ctx.contextlevel = :contextlevel
 263                   WHERE ctx.id $insql";
 264          $enrolsids = $DB->get_fieldset_sql($sql, $params);
 265          if (!empty($enrolsids)) {
 266              list($insql, $inparams) = $DB->get_in_or_equal($enrolsids, SQL_PARAMS_NAMED);
 267              static::delete_user_data($insql, $inparams);
 268          }
 269      }
 270  
 271      /**
 272       * Delete data from $tablename with the IDs returned by $sql query.
 273       *
 274       * @param  string $sql    SQL query for getting the IDs of the uer enrolments entries to delete.
 275       * @param  array  $params SQL params for the query.
 276       */
 277      protected static function delete_user_data(string $sql, array $params) {
 278          global $DB;
 279  
 280          $DB->delete_records_select('user_enrolments', "id $sql", $params);
 281      }
 282  
 283      /**
 284       * Get the subcontext for export.
 285       *
 286       * @param array $subcontext Any additional subcontext to use.
 287       * @return array The array containing the full subcontext, i.e. [enrolments, subcontext]
 288       */
 289      public static function get_subcontext(array $subcontext) {
 290          return array_merge(
 291              [get_string('privacy:metadata:user_enrolments', 'core_enrol')],
 292              $subcontext
 293          );
 294      }
 295  }