Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   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  namespace mod_bigbluebuttonbn\privacy;
  18  
  19  use core_privacy\local\metadata\collection;
  20  use core_privacy\local\request\approved_contextlist;
  21  use core_privacy\local\request\approved_userlist;
  22  use core_privacy\local\request\contextlist;
  23  use core_privacy\local\request\helper;
  24  use core_privacy\local\request\transform;
  25  use core_privacy\local\request\userlist;
  26  use core_privacy\local\request\writer;
  27  
  28  /**
  29   * Privacy class for requesting user data.
  30   *
  31   * @package   mod_bigbluebuttonbn
  32   * @copyright 2018 - present, Blindside Networks Inc
  33   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
  35   */
  36  class provider implements
  37      // This plugin has data.
  38      \core_privacy\local\metadata\provider,
  39  
  40      // This plugin currently implements the original plugin\provider interface.
  41      \core_privacy\local\request\plugin\provider,
  42  
  43      // This plugin is capable of determining which users have data within it.
  44      \core_privacy\local\request\core_userlist_provider {
  45  
  46      /**
  47       * Returns metadata.
  48       *
  49       * @param collection $collection The initialised collection to add items to.
  50       * @return collection A listing of user data stored through this system.
  51       */
  52      public static function get_metadata(collection $collection): collection {
  53  
  54           // The table bigbluebuttonbn stores only the room properties.
  55           // However, there is a chance that some personal information is stored as metadata.
  56           // This would be done in the column 'participants' where rules can be set to define BBB roles.
  57           // It is fair to say that only the userid is stored, which is useless if user is removed.
  58           // But if this is a concern a refactoring on the way the rules are stored will be required.
  59          $collection->add_database_table('bigbluebuttonbn', [
  60              'participants' => 'privacy:metadata:bigbluebuttonbn:participants',
  61          ], 'privacy:metadata:bigbluebuttonbn');
  62  
  63          // The table bigbluebuttonbn_logs stores events triggered by users when using the plugin.
  64          // Some personal information along with the resource accessed is stored.
  65          $collection->add_database_table('bigbluebuttonbn_logs', [
  66              'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid',
  67              'timecreated' => 'privacy:metadata:bigbluebuttonbn_logs:timecreated',
  68              'meetingid' => 'privacy:metadata:bigbluebuttonbn_logs:meetingid',
  69              'log' => 'privacy:metadata:bigbluebuttonbn_logs:log',
  70              'meta' => 'privacy:metadata:bigbluebuttonbn_logs:meta',
  71          ], 'privacy:metadata:bigbluebuttonbn_logs');
  72  
  73          $collection->add_database_table('bigbluebuttonbn_recordings', [
  74              'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid',
  75          ], 'privacy:metadata:bigbluebuttonbn_recordings');
  76  
  77          // Personal information has to be passed to BigBlueButton.
  78          // This includes the user ID and fullname.
  79          $collection->add_external_location_link('bigbluebutton', [
  80                  'userid' => 'privacy:metadata:bigbluebutton:userid',
  81                  'fullname' => 'privacy:metadata:bigbluebutton:fullname',
  82              ], 'privacy:metadata:bigbluebutton');
  83  
  84          return $collection;
  85      }
  86  
  87      /**
  88       * Get the list of contexts that contain user information for the specified user.
  89       *
  90       * @param   int           $userid       The user to search.
  91       * @return  contextlist   $contextlist  The list of contexts used in this plugin.
  92       */
  93      public static function get_contexts_for_userid(int $userid): contextlist {
  94          // If user was already deleted, do nothing.
  95          if (!\core_user::get_user($userid)) {
  96              return new contextlist();
  97          }
  98          // Fetch all bigbluebuttonbn logs.
  99          $sql = "SELECT c.id
 100                    FROM {context} c
 101              INNER JOIN {course_modules} cm
 102                      ON cm.id = c.instanceid
 103                     AND c.contextlevel = :contextlevel
 104              INNER JOIN {modules} m
 105                      ON m.id = cm.module
 106                     AND m.name = :modname
 107              INNER JOIN {bigbluebuttonbn} bigbluebuttonbn
 108                      ON bigbluebuttonbn.id = cm.instance
 109              INNER JOIN {bigbluebuttonbn_logs} bigbluebuttonbnlogs
 110                      ON bigbluebuttonbnlogs.bigbluebuttonbnid = bigbluebuttonbn.id
 111                   WHERE bigbluebuttonbnlogs.userid = :userid";
 112  
 113          $params = [
 114              'modname' => 'bigbluebuttonbn',
 115              'contextlevel' => CONTEXT_MODULE,
 116              'userid' => $userid,
 117          ];
 118          $contextlist = new contextlist();
 119          $contextlist->add_from_sql($sql, $params);
 120          return $contextlist;
 121      }
 122  
 123      /**
 124       * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
 125       *
 126       * @param approved_contextlist $contextlist a list of contexts approved for export.
 127       */
 128      public static function export_user_data(approved_contextlist $contextlist) {
 129          global $DB;
 130  
 131          // Filter out any contexts that are not related to modules.
 132          $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
 133              if ($context->contextlevel == CONTEXT_MODULE) {
 134                  $carry[] = $context->instanceid;
 135              }
 136              return $carry;
 137          }, []);
 138  
 139          if (empty($cmids)) {
 140              return;
 141          }
 142  
 143          $user = $contextlist->get_user();
 144  
 145          // Get all the bigbluebuttonbn activities associated with the above course modules.
 146          $instanceidstocmids = self::get_instance_ids_to_cmids_from_cmids($cmids);
 147          $instanceids = array_keys($instanceidstocmids);
 148  
 149          list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED);
 150          $params = array_merge($inparams, ['userid' => $user->id]);
 151          $recordset = $DB->get_recordset_select(
 152              'bigbluebuttonbn_logs',
 153              "bigbluebuttonbnid $insql AND userid = :userid",
 154              $params,
 155              'timecreated, id'
 156          );
 157          self::recordset_loop_and_export($recordset, 'bigbluebuttonbnid', [],
 158              function($carry, $record) use ($user, $instanceidstocmids) {
 159                  $carry[] = [
 160                      'timecreated' => transform::datetime($record->timecreated),
 161                      'meetingid' => $record->meetingid,
 162                      'log' => $record->log,
 163                      'meta' => $record->meta,
 164                    ];
 165                  return $carry;
 166              },
 167              function($instanceid, $data) use ($user, $instanceidstocmids) {
 168                  $context = \context_module::instance($instanceidstocmids[$instanceid]);
 169                  $contextdata = helper::get_context_data($context, $user);
 170                  $finaldata = (object) array_merge((array) $contextdata, ['logs' => $data]);
 171                  helper::export_context_files($context, $user);
 172                  writer::with_context($context)->export_data([], $finaldata);
 173              }
 174          );
 175      }
 176  
 177      /**
 178       * Delete all data for all users in the specified context.
 179       *
 180       * @param \context $context the context to delete in.
 181       */
 182      public static function delete_data_for_all_users_in_context(\context $context) {
 183          global $DB;
 184  
 185          if (!$context instanceof \context_module) {
 186              return;
 187          }
 188  
 189          $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
 190          $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid]);
 191      }
 192  
 193      /**
 194       * Delete all user data for the specified user, in the specified contexts.
 195       *
 196       * @param approved_contextlist $contextlist a list of contexts approved for deletion.
 197       */
 198      public static function delete_data_for_user(approved_contextlist $contextlist) {
 199          global $DB;
 200          $count = $contextlist->count();
 201          if (empty($count)) {
 202              return;
 203          }
 204          $userid = $contextlist->get_user()->id;
 205          foreach ($contextlist->get_contexts() as $context) {
 206              if (!$context instanceof \context_module) {
 207                  return;
 208              }
 209              $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
 210              $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid, 'userid' => $userid]);
 211          }
 212      }
 213  
 214      /**
 215       * Return a dict of bigbluebuttonbn IDs mapped to their course module ID.
 216       *
 217       * @param array $cmids The course module IDs.
 218       * @return array In the form of [$bigbluebuttonbnid => $cmid].
 219       */
 220      protected static function get_instance_ids_to_cmids_from_cmids(array $cmids) {
 221          global $DB;
 222  
 223          list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
 224          $sql = "SELECT bigbluebuttonbn.id, cm.id AS cmid
 225                   FROM {bigbluebuttonbn} bigbluebuttonbn
 226                   JOIN {modules} m
 227                     ON m.name = :bigbluebuttonbn
 228                   JOIN {course_modules} cm
 229                     ON cm.instance = bigbluebuttonbn.id
 230                    AND cm.module = m.id
 231                  WHERE cm.id $insql";
 232          $params = array_merge($inparams, ['bigbluebuttonbn' => 'bigbluebuttonbn']);
 233  
 234          return $DB->get_records_sql_menu($sql, $params);
 235      }
 236  
 237      /**
 238       * Loop and export from a recordset.
 239       *
 240       * @param \moodle_recordset $recordset The recordset.
 241       * @param string $splitkey The record key to determine when to export.
 242       * @param mixed $initial The initial data to reduce from.
 243       * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
 244       * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
 245       * @return void
 246       */
 247      protected static function recordset_loop_and_export(
 248          \moodle_recordset $recordset,
 249          $splitkey,
 250          $initial,
 251          callable $reducer,
 252          callable $export
 253      ) {
 254          $data = $initial;
 255          $lastid = null;
 256  
 257          foreach ($recordset as $record) {
 258              if ($lastid && $record->{$splitkey} != $lastid) {
 259                  $export($lastid, $data);
 260                  $data = $initial;
 261              }
 262              $data = $reducer($data, $record);
 263              $lastid = $record->{$splitkey};
 264          }
 265          $recordset->close();
 266  
 267          if (!empty($lastid)) {
 268              $export($lastid, $data);
 269          }
 270      }
 271  
 272      /**
 273       * Get the list of users who have data within a context.
 274       *
 275       * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
 276       */
 277      public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
 278          $context = $userlist->get_context();
 279  
 280          if (!$context instanceof \context_module) {
 281              return;
 282          }
 283  
 284          $params = [
 285              'instanceid' => $context->instanceid,
 286              'modulename' => 'bigbluebuttonbn',
 287          ];
 288  
 289          $sql = "SELECT bnl.userid
 290                    FROM {course_modules} cm
 291                    JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
 292                    JOIN {bigbluebuttonbn} bn ON bn.id = cm.instance
 293                    JOIN {bigbluebuttonbn_logs} bnl ON bnl.bigbluebuttonbnid = bn.id
 294                   WHERE cm.id = :instanceid";
 295  
 296          $userlist->add_from_sql('userid', $sql, $params);
 297      }
 298  
 299      /**
 300       * Delete multiple users within a single context.
 301       *
 302       * @param   approved_userlist       $userlist The approved context and user information to delete information for.
 303       */
 304      public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
 305          global $DB;
 306  
 307          $context = $userlist->get_context();
 308          $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]);
 309  
 310          list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 311          $params = array_merge(['bigbluebuttonbnid' => $cm->instance], $userinparams);
 312          $sql = "bigbluebuttonbnid = :bigbluebuttonbnid AND userid {$userinsql}";
 313  
 314          $DB->delete_records_select('bigbluebuttonbn_logs', $sql, $params);
 315      }
 316  }