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 core_backup.
  19   *
  20   * @package    core_backup
  21   * @copyright  2018 Mark Nelson <markn@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_backup\privacy;
  26  
  27  use core_privacy\local\metadata\collection;
  28  use core_privacy\local\request\approved_contextlist;
  29  use core_privacy\local\request\contextlist;
  30  use core_privacy\local\request\transform;
  31  use core_privacy\local\request\writer;
  32  use core_privacy\local\request\userlist;
  33  use core_privacy\local\request\approved_userlist;
  34  
  35  defined('MOODLE_INTERNAL') || die();
  36  
  37  require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
  38  
  39  /**
  40   * Privacy Subsystem implementation for core_backup.
  41   *
  42   * @copyright  2018 Mark Nelson <markn@moodle.com>
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class provider implements
  46      \core_privacy\local\metadata\provider,
  47      \core_privacy\local\request\core_userlist_provider,
  48      \core_privacy\local\request\subsystem\provider {
  49  
  50      /**
  51       * Return the fields which contain personal data.
  52       *
  53       * @param collection $items a reference to the collection to use to store the metadata.
  54       * @return collection the updated collection of metadata items.
  55       */
  56      public static function get_metadata(collection $items) : collection {
  57          $items->link_external_location(
  58              'Backup',
  59              [
  60                  'detailsofarchive' => 'privacy:metadata:backup:detailsofarchive'
  61              ],
  62              'privacy:metadata:backup:externalpurpose'
  63          );
  64  
  65          $items->add_database_table(
  66              'backup_controllers',
  67              [
  68                  'operation' => 'privacy:metadata:backup_controllers:operation',
  69                  'type' => 'privacy:metadata:backup_controllers:type',
  70                  'itemid' => 'privacy:metadata:backup_controllers:itemid',
  71                  'timecreated' => 'privacy:metadata:backup_controllers:timecreated',
  72                  'timemodified' => 'privacy:metadata:backup_controllers:timemodified'
  73              ],
  74              'privacy:metadata:backup_controllers'
  75          );
  76  
  77          return $items;
  78      }
  79  
  80      /**
  81       * Get the list of contexts that contain user information for the specified user.
  82       *
  83       * @param int $userid The user to search.
  84       * @return contextlist The contextlist containing the list of contexts used in this plugin.
  85       */
  86      public static function get_contexts_for_userid(int $userid) : contextlist {
  87          $contextlist = new contextlist();
  88  
  89          $sql = "SELECT ctx.id
  90                    FROM {backup_controllers} bc
  91                    JOIN {context} ctx
  92                          ON ctx.instanceid = bc.itemid
  93                         AND ctx.contextlevel = :contextlevel
  94                         AND bc.type = :type
  95                   WHERE bc.userid = :userid";
  96          $params = [
  97              'contextlevel' => CONTEXT_COURSE,
  98              'userid' => $userid,
  99              'type' => 'course',
 100          ];
 101          $contextlist->add_from_sql($sql, $params);
 102  
 103          $sql = "SELECT ctx.id
 104                    FROM {backup_controllers} bc
 105                    JOIN {course_sections} c
 106                          ON bc.itemid = c.id
 107                         AND bc.type = :type
 108                    JOIN {context} ctx
 109                          ON ctx.instanceid = c.course
 110                         AND ctx.contextlevel = :contextlevel
 111                   WHERE bc.userid = :userid";
 112          $params = [
 113              'contextlevel' => CONTEXT_COURSE,
 114              'userid' => $userid,
 115              'type' => 'section',
 116          ];
 117          $contextlist->add_from_sql($sql, $params);
 118  
 119          $sql = "SELECT ctx.id
 120                    FROM {backup_controllers} bc
 121                    JOIN {context} ctx
 122                          ON ctx.instanceid = bc.itemid
 123                         AND ctx.contextlevel = :contextlevel
 124                         AND bc.type = :type
 125                   WHERE bc.userid = :userid";
 126          $params = [
 127              'contextlevel' => CONTEXT_MODULE,
 128              'userid' => $userid,
 129              'type' => 'activity',
 130          ];
 131          $contextlist->add_from_sql($sql, $params);
 132  
 133          return $contextlist;
 134      }
 135  
 136      /**
 137       * Get the list of users within a specific context.
 138       *
 139       * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
 140       */
 141      public static function get_users_in_context(userlist $userlist) {
 142          $context = $userlist->get_context();
 143  
 144          if ($context instanceof \context_course) {
 145              $params = ['courseid' => $context->instanceid];
 146  
 147              $sql = "SELECT bc.userid
 148                        FROM {backup_controllers} bc
 149                       WHERE bc.itemid = :courseid
 150                             AND bc.type = :typecourse";
 151  
 152              $courseparams = ['typecourse' => 'course'] + $params;
 153  
 154              $userlist->add_from_sql('userid', $sql, $courseparams);
 155  
 156              $sql = "SELECT bc.userid
 157                        FROM {backup_controllers} bc
 158                        JOIN {course_sections} c
 159                             ON bc.itemid = c.id
 160                       WHERE c.course = :courseid
 161                             AND bc.type = :typesection";
 162  
 163              $sectionparams = ['typesection' => 'section'] + $params;
 164  
 165              $userlist->add_from_sql('userid', $sql, $sectionparams);
 166          }
 167  
 168          if ($context instanceof \context_module) {
 169              $params = [
 170                  'cmid' => $context->instanceid,
 171                  'typeactivity' => 'activity'
 172              ];
 173  
 174              $sql = "SELECT bc.userid
 175                        FROM {backup_controllers} bc
 176                       WHERE bc.itemid = :cmid
 177                             AND bc.type = :typeactivity";
 178  
 179              $userlist->add_from_sql('userid', $sql, $params);
 180          }
 181      }
 182  
 183      /**
 184       * Export all user data for the specified user, in the specified contexts.
 185       *
 186       * @param approved_contextlist $contextlist The approved contexts to export information for.
 187       */
 188      public static function export_user_data(approved_contextlist $contextlist) {
 189          global $DB;
 190  
 191          if (empty($contextlist->count())) {
 192              return;
 193          }
 194  
 195          $user = $contextlist->get_user();
 196  
 197          list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
 198  
 199          $sql = "SELECT bc.*
 200                    FROM {backup_controllers} bc
 201                    JOIN {context} ctx
 202                      ON ctx.instanceid = bc.itemid AND ctx.contextlevel = :contextlevel
 203                   WHERE ctx.id {$contextsql}
 204                     AND bc.userid = :userid
 205                ORDER BY bc.timecreated ASC";
 206          $params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $user->id] + $contextparams;
 207          $backupcontrollers = $DB->get_recordset_sql($sql, $params);
 208          self::recordset_loop_and_export($backupcontrollers, 'itemid', [], function($carry, $record) {
 209              $carry[] = [
 210                  'operation' => $record->operation,
 211                  'type' => $record->type,
 212                  'itemid' => $record->itemid,
 213                  'timecreated' => transform::datetime($record->timecreated),
 214                  'timemodified' => transform::datetime($record->timemodified),
 215              ];
 216              return $carry;
 217          }, function($courseid, $data) {
 218              $context = \context_course::instance($courseid);
 219              $finaldata = (object) $data;
 220              writer::with_context($context)->export_data([get_string('backup'), $courseid], $finaldata);
 221          });
 222      }
 223  
 224      /**
 225       * Delete all user data which matches the specified context.
 226       * Only dealing with the specific context - not it's child contexts.
 227       *
 228       * @param \context $context A user context.
 229       */
 230      public static function delete_data_for_all_users_in_context(\context $context) {
 231          global $DB;
 232  
 233          if ($context instanceof \context_course) {
 234              $sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = ?) AND type = ?";
 235              $DB->delete_records_select('backup_controllers', $sectionsql, [$context->instanceid, \backup::TYPE_1SECTION]);
 236              $DB->delete_records('backup_controllers', ['itemid' => $context->instanceid, 'type' => \backup::TYPE_1COURSE]);
 237          }
 238          if ($context instanceof \context_module) {
 239              $DB->delete_records('backup_controllers', ['itemid' => $context->instanceid, 'type' => \backup::TYPE_1ACTIVITY]);
 240          }
 241          return;
 242      }
 243  
 244      /**
 245       * Delete multiple users within a single context.
 246       * Only dealing with the specific context - not it's child contexts.
 247       *
 248       * @param approved_userlist $userlist The approved context and user information to delete information for.
 249       */
 250      public static function delete_data_for_users(approved_userlist $userlist) {
 251          global $DB;
 252  
 253          if (empty($userlist->get_userids())) {
 254              return;
 255          }
 256  
 257          $context = $userlist->get_context();
 258          if ($context instanceof \context_course) {
 259              list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 260              $select = "itemid = :itemid AND userid {$usersql} AND type = :type";
 261              $params = $userparams;
 262              $params['itemid'] = $context->instanceid;
 263              $params['type'] = \backup::TYPE_1COURSE;
 264  
 265              $DB->delete_records_select('backup_controllers', $select, $params);
 266  
 267              $params = $userparams;
 268              $params['course'] = $context->instanceid;
 269              $params['type'] = \backup::TYPE_1SECTION;
 270              $sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = :course)";
 271              $select = $sectionsql . " AND userid {$usersql} AND type = :type";
 272              $DB->delete_records_select('backup_controllers', $select, $params);
 273          }
 274          if ($context instanceof \context_module) {
 275              list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 276              $select = "itemid = :itemid AND userid {$usersql} AND type = :type";
 277              $params = $userparams;
 278              $params['itemid'] = $context->instanceid;
 279              $params['type'] = \backup::TYPE_1ACTIVITY;
 280  
 281              // Delete activity backup data.
 282              $select = "itemid = :itemid AND type = :type AND userid {$usersql}";
 283              $params = ['itemid' => $context->instanceid, 'type' => 'activity'] + $userparams;
 284              $DB->delete_records_select('backup_controllers', $select, $params);
 285          }
 286      }
 287  
 288      /**
 289       * Delete all user data for the specified user, in the specified contexts.
 290       * Only dealing with the specific context - not it's child contexts.
 291       *
 292       * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
 293       */
 294      public static function delete_data_for_user(approved_contextlist $contextlist) {
 295          global $DB;
 296  
 297          if (empty($contextlist->count())) {
 298              return;
 299          }
 300  
 301          $userid = $contextlist->get_user()->id;
 302          foreach ($contextlist->get_contexts() as $context) {
 303              if ($context instanceof \context_course) {
 304                  $select = "itemid = :itemid AND userid = :userid AND type = :type";
 305                  $params = [
 306                      'userid' => $userid,
 307                      'itemid' => $context->instanceid,
 308                      'type' => \backup::TYPE_1COURSE
 309                  ];
 310  
 311                  $DB->delete_records_select('backup_controllers', $select, $params);
 312  
 313                  $params = [
 314                      'userid' => $userid,
 315                      'course' => $context->instanceid,
 316                      'type' => \backup::TYPE_1SECTION
 317                  ];
 318                  $sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = :course)";
 319                  $select = $sectionsql . " AND userid = :userid AND type = :type";
 320                  $DB->delete_records_select('backup_controllers', $select, $params);
 321              }
 322              if ($context instanceof \context_module) {
 323                  list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 324                  $select = "itemid = :itemid AND userid = :userid AND type = :type";
 325                  $params = [
 326                      'itemid' => $context->instanceid,
 327                      'userid' => $userid,
 328                      'type' => \backup::TYPE_1ACTIVITY
 329                  ];
 330  
 331                  $DB->delete_records_select('backup_controllers', $select, $params);
 332              }
 333  
 334          }
 335      }
 336  
 337      /**
 338       * Loop and export from a recordset.
 339       *
 340       * @param \moodle_recordset $recordset The recordset.
 341       * @param string $splitkey The record key to determine when to export.
 342       * @param mixed $initial The initial data to reduce from.
 343       * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
 344       * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
 345       * @return void
 346       */
 347      protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
 348              callable $reducer, callable $export) {
 349          $data = $initial;
 350          $lastid = null;
 351  
 352          foreach ($recordset as $record) {
 353              if ($lastid && $record->{$splitkey} != $lastid) {
 354                  $export($lastid, $data);
 355                  $data = $initial;
 356              }
 357              $data = $reducer($data, $record);
 358              $lastid = $record->{$splitkey};
 359          }
 360          $recordset->close();
 361  
 362          if (!empty($lastid)) {
 363              $export($lastid, $data);
 364          }
 365      }
 366  }