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]

   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 class for requesting user data.
  19   *
  20   * @package    core_course
  21   * @copyright  2018 Adrian Greeve <adrian@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_course\privacy;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use \core_privacy\local\metadata\collection;
  30  use \core_privacy\local\request\contextlist;
  31  use \core_privacy\local\request\approved_contextlist;
  32  use \core_privacy\local\request\approved_userlist;
  33  use \core_privacy\local\request\transform;
  34  use \core_privacy\local\request\userlist;
  35  use \core_privacy\local\request\writer;
  36  
  37  /**
  38   * Privacy class for requesting user data.
  39   *
  40   * @copyright  2018 Adrian Greeve <adrian@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\context_aware_provider,
  46          \core_privacy\local\request\core_userlist_provider,
  47          \core_privacy\local\request\plugin\provider,
  48          \core_privacy\local\request\user_preference_provider {
  49  
  50      /**
  51       * Returns meta data about this system.
  52       *
  53       * @param   collection $collection The initialised collection to add items to.
  54       * @return  collection A listing of user data stored through this system.
  55       */
  56      public static function get_metadata(collection $collection) : collection {
  57          $collection->add_subsystem_link('core_completion', [], 'privacy:metadata:completionsummary');
  58          $collection->add_subsystem_link('core_favourites', [], 'privacy:metadata:favouritessummary');
  59          $collection->add_subsystem_link('core_favourites', [], 'privacy:metadata:activityfavouritessummary');
  60          $collection->add_user_preference('coursecat_management_perpage', 'privacy:perpage');
  61          return $collection;
  62      }
  63  
  64      /**
  65       * Get the list of contexts that contain user information for the specified user.
  66       *
  67       * @param   int $userid The user to search.
  68       * @return  contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
  69       */
  70      public static function get_contexts_for_userid(int $userid) : contextlist {
  71          list($join, $where, $params) = \core_completion\privacy\provider::get_course_completion_join_sql($userid, 'cc', 'c.id');
  72          $sql = "SELECT ctx.id
  73                  FROM {context} ctx
  74                  JOIN {course} c ON ctx.instanceid = c.id AND ctx.contextlevel = :contextcourse
  75                  {$join}
  76                  WHERE {$where}";
  77          $params['contextcourse'] = CONTEXT_COURSE;
  78          $contextlist = new contextlist();
  79          $contextlist->add_from_sql($sql, $params);
  80  
  81          \core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $userid, 'core_course', 'courses');
  82  
  83          return $contextlist;
  84      }
  85  
  86      /**
  87       * Get the list of users who have data within a context.
  88       *
  89       * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
  90       */
  91      public static function get_users_in_context(userlist $userlist) {
  92          $context = $userlist->get_context();
  93  
  94          if (!$context instanceof \context_course) {
  95              return;
  96          }
  97  
  98          \core_completion\privacy\provider::add_course_completion_users_to_userlist($userlist);
  99          \core_favourites\privacy\provider::add_userids_for_context($userlist, 'courses');
 100      }
 101  
 102      /**
 103       * Export all user data for the specified user, in the specified contexts.
 104       *
 105       * @param approved_contextlist $contextlist The approved contexts to export information for.
 106       */
 107      public static function export_user_data(approved_contextlist $contextlist) {
 108          global $DB;
 109  
 110          // Get the course.
 111          list($select, $params) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
 112          $params['contextcourse'] = CONTEXT_COURSE;
 113  
 114          $sql = "SELECT c.*
 115                  FROM {course} c
 116                  JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextcourse
 117                  WHERE ctx.id $select";
 118  
 119          $courses = $DB->get_recordset_sql($sql, $params);
 120  
 121          foreach ($courses as $course) {
 122              $coursecompletion = \core_completion\privacy\provider::get_course_completion_info($contextlist->get_user(), $course);
 123              writer::with_context(\context_course::instance($course->id))->export_data(
 124                      [get_string('privacy:completionpath', 'course')], (object) $coursecompletion);
 125              // Get user's favourites information for the particular course.
 126              $coursefavourite = \core_favourites\privacy\provider::get_favourites_info_for_user($contextlist->get_user()->id,
 127                      \context_course::instance($course->id), 'core_course', 'courses', $course->id);
 128              if ($coursefavourite) { // If the course has been favourited by the user, include it in the export.
 129                  writer::with_context(\context_course::instance($course->id))->export_data(
 130                          [get_string('privacy:favouritespath', 'course')], (object) $coursefavourite);
 131              }
 132          }
 133          $courses->close();
 134      }
 135  
 136      /**
 137       * Give the component a chance to include any contextual information deemed relevant to any child contexts which are
 138       * exporting personal data.
 139       *
 140       * By giving the component access to the full list of contexts being exported across all components, it can determine whether a
 141       * descendant context is being exported, and decide whether to add relevant contextual information about itself. Having access
 142       * to the full list of contexts being exported is what makes this component a context aware provider.
 143       *
 144       * E.g.
 145       * If, during the core export process, a course module is included in the contextlist_collection but the course containing the
 146       * module is not (perhaps there's no longer a user enrolment), then the course should include general contextual information in
 147       * the export so we know basic details about which course the module belongs to. This method allows the course to make that
 148       * decision, based on the existence of any decendant module contexts in the collection.
 149       *
 150       * @param \core_privacy\local\request\contextlist_collection $contextlistcollection
 151       */
 152      public static function export_context_data(\core_privacy\local\request\contextlist_collection $contextlistcollection) {
 153          global $DB;
 154  
 155          $coursecontextids = $DB->get_records_menu('context', ['contextlevel' => CONTEXT_COURSE], '', 'id, instanceid');
 156          $courseids = [];
 157          foreach ($contextlistcollection as $component) {
 158              foreach ($component->get_contexts() as $context) {
 159                  // All course contexts have been accounted for, so skip all checks.
 160                  if (empty($coursecontextids)) {
 161                      break;
 162                  }
 163                  // Only course, module, and block contexts are checked.
 164                  if (in_array($context->contextlevel, [CONTEXT_USER, CONTEXT_SYSTEM, CONTEXT_COURSECAT])) {
 165                      continue;
 166                  }
 167                  // If the context is a course, then we just add it without the need to check context path.
 168                  if ($context->contextlevel == CONTEXT_COURSE) {
 169                      $courseids[$context->id] = $context->instanceid;
 170                      unset($coursecontextids[$context->id]);
 171                      continue;
 172                  }
 173                  // Otherwise, we need to check all the course context paths, to see if this context is a descendant.
 174                  foreach ($coursecontextids as $contextid => $instanceid) {
 175                      if (stripos($context->path, '/' . $contextid . '/') !== false) {
 176                          $courseids[$contextid] = $instanceid;
 177                          unset($coursecontextids[$contextid]);
 178                      }
 179                  }
 180              }
 181          }
 182          if (empty($courseids)) {
 183              return;
 184          }
 185  
 186          // Export general data for these contexts.
 187          list($sql, $params) = $DB->get_in_or_equal($courseids);
 188          $sql = 'id ' . $sql;
 189          $coursedata = $DB->get_records_select('course', $sql, $params);
 190  
 191          foreach ($coursedata as $course) {
 192              $context = \context_course::instance($course->id);
 193              $courseformat = $course->format !== 'site' ? get_string('pluginname', 'format_' . $course->format) : get_string('site');
 194              $data = (object) [
 195                  'fullname' => format_string($course->fullname, true, ['context' => $context]),
 196                  'shortname' => $course->shortname,
 197                  'idnumber' => $course->idnumber,
 198                  'summary' => format_text(
 199                      writer::with_context($context)->rewrite_pluginfile_urls(
 200                          [],
 201                          'course',
 202                          'summary',
 203                          0,
 204                          $course->summary
 205                      ), $course->summaryformat, ['context' => $context]),
 206                  'format' => $courseformat,
 207                  'startdate' => transform::datetime($course->startdate),
 208                  'enddate' => transform::datetime($course->enddate)
 209              ];
 210              writer::with_context($context)
 211                      ->export_area_files([], 'course', 'summary', 0)
 212                      ->export_area_files([], 'course', 'overviewfiles', 0)
 213                      ->export_data([], $data);
 214          }
 215      }
 216  
 217      /**
 218       * Export all user preferences for the plugin.
 219       *
 220       * @param int $userid The userid of the user whose data is to be exported.
 221       */
 222      public static function export_user_preferences(int $userid) {
 223          $perpage = get_user_preferences('coursecat_management_perpage', null, $userid);
 224          if (isset($perpage)) {
 225              writer::export_user_preference('core_course',
 226                  'coursecat_management_perpage',
 227                  $perpage,
 228                  get_string('privacy:perpage', 'course')
 229              );
 230          }
 231      }
 232  
 233      /**
 234       * Delete all data for all users in the specified context.
 235       *
 236       * @param \context $context The specific context to delete data for.
 237       */
 238      public static function delete_data_for_all_users_in_context(\context $context) {
 239          // Check what context we've been delivered.
 240          if (!$context instanceof \context_course) {
 241              return;
 242          }
 243          // Delete course completion data.
 244          \core_completion\privacy\provider::delete_completion(null, $context->instanceid);
 245          // Delete course favourite data.
 246          \core_favourites\privacy\provider::delete_favourites_for_all_users($context, 'core_course',
 247                  'courses');
 248      }
 249  
 250      /**
 251       * Delete all user data for the specified user, in the specified contexts.
 252       *
 253       * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
 254       */
 255      public static function delete_data_for_user(approved_contextlist $contextlist) {
 256          foreach ($contextlist as $context) {
 257              // Check what context we've been delivered.
 258              if ($context instanceof \context_course) {
 259                  // Delete course completion data.
 260                  \core_completion\privacy\provider::delete_completion($contextlist->get_user(), $context->instanceid);
 261                  // Delete course favourite data.
 262                  \core_favourites\privacy\provider::delete_favourites_for_user($contextlist, 'core_course',
 263                      'courses');
 264              }
 265          }
 266      }
 267  
 268      /**
 269       * Delete multiple users within a single context.
 270       *
 271       * @param   approved_userlist       $userlist The approved context and user information to delete information for.
 272       */
 273      public static function delete_data_for_users(approved_userlist $userlist) {
 274          $context = $userlist->get_context();
 275  
 276          // Check what context we've been delivered.
 277          if (!$context instanceof \context_course) {
 278              return;
 279          }
 280          // Delete course completion data.
 281          \core_completion\privacy\provider::delete_completion_by_approved_userlist($userlist, $context->instanceid);
 282          // Delete course favourite data.
 283          \core_favourites\privacy\provider::delete_favourites_for_userlist($userlist, 'courses');
 284      }
 285  }