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 400 and 403] [Versions 401 and 403] [Versions 402 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   * Library calls for Moodle and BigBlueButton.
  19   *
  20   * @package   mod_bigbluebuttonbn
  21   * @copyright 2010 onwards, Blindside Networks Inc
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
  24   * @author    Fred Dixon  (ffdixon [at] blindsidenetworks [dt] com)
  25   */
  26  defined('MOODLE_INTERNAL') || die;
  27  
  28  use core_calendar\action_factory;
  29  use core_calendar\local\event\entities\action_interface;
  30  use mod_bigbluebuttonbn\completion\custom_completion;
  31  use mod_bigbluebuttonbn\extension;
  32  use mod_bigbluebuttonbn\instance;
  33  use mod_bigbluebuttonbn\local\bigbluebutton;
  34  use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
  35  use mod_bigbluebuttonbn\local\helpers\files;
  36  use mod_bigbluebuttonbn\local\helpers\mod_helper;
  37  use mod_bigbluebuttonbn\local\helpers\reset;
  38  use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
  39  use mod_bigbluebuttonbn\logger;
  40  use mod_bigbluebuttonbn\meeting;
  41  use mod_bigbluebuttonbn\recording;
  42  use mod_bigbluebuttonbn\local\config;
  43  
  44  global $CFG;
  45  
  46  /**
  47   * Indicates API features that the bigbluebuttonbn supports.
  48   *
  49   * @param string $feature
  50   * @return mixed True if yes (some features may use other values)
  51   * @uses FEATURE_IDNUMBER
  52   * @uses FEATURE_GROUPS
  53   * @uses FEATURE_GROUPINGS
  54   * @uses FEATURE_GROUPMEMBERSONLY
  55   * @uses FEATURE_MOD_INTRO
  56   * @uses FEATURE_BACKUP_MOODLE2
  57   * @uses FEATURE_COMPLETION_TRACKS_VIEWS
  58   * @uses FEATURE_COMPLETION_HAS_RULES
  59   * @uses FEATURE_GRADE_HAS_GRADE
  60   * @uses FEATURE_GRADE_OUTCOMES
  61   * @uses FEATURE_SHOW_DESCRIPTION
  62   */
  63  function bigbluebuttonbn_supports($feature) {
  64      if (!$feature) {
  65          return null;
  66      }
  67      $features = [
  68          FEATURE_IDNUMBER => true,
  69          FEATURE_GROUPS => true,
  70          FEATURE_GROUPINGS => true,
  71          FEATURE_MOD_INTRO => true,
  72          FEATURE_BACKUP_MOODLE2 => true,
  73          FEATURE_COMPLETION_TRACKS_VIEWS => true,
  74          FEATURE_COMPLETION_HAS_RULES => true,
  75          FEATURE_GRADE_HAS_GRADE => false,
  76          FEATURE_GRADE_OUTCOMES => false,
  77          FEATURE_SHOW_DESCRIPTION => true,
  78          FEATURE_MOD_PURPOSE => MOD_PURPOSE_OTHER
  79      ];
  80      if (isset($features[(string) $feature])) {
  81          return $features[$feature];
  82      }
  83      return null;
  84  }
  85  
  86  /**
  87   * Given an object containing all the necessary data,
  88   * (defined by the form in mod_form.php) this function
  89   * will create a new instance and return the id number
  90   * of the new instance.
  91   *
  92   * @param stdClass $bigbluebuttonbn An object from the form in mod_form.php
  93   * @return int The id of the newly inserted bigbluebuttonbn record
  94   */
  95  function bigbluebuttonbn_add_instance($bigbluebuttonbn) {
  96      global $DB;
  97      // Excecute preprocess.
  98      mod_helper::process_pre_save($bigbluebuttonbn);
  99      // Pre-set initial values.
 100      $bigbluebuttonbn->presentation = files::save_media_file($bigbluebuttonbn);
 101      // Encode meetingid.
 102      $bigbluebuttonbn->meetingid = meeting::get_unique_meetingid_seed();
 103      [$bigbluebuttonbn->guestlinkuid, $bigbluebuttonbn->guestpassword] =
 104          \mod_bigbluebuttonbn\plugin::generate_guest_meeting_credentials();
 105      // Insert a record.
 106      $bigbluebuttonbn->id = $DB->insert_record('bigbluebuttonbn', $bigbluebuttonbn);
 107      // Log insert action.
 108      logger::log_instance_created($bigbluebuttonbn);
 109      // Complete the process.
 110      mod_helper::process_post_save($bigbluebuttonbn);
 111  
 112      // Call any active subplugin so to signal a new creation.
 113      extension::add_instance($bigbluebuttonbn);
 114      return $bigbluebuttonbn->id;
 115  }
 116  
 117  /**
 118   * Given an object containing all the necessary data,
 119   * (defined by the form in mod_form.php) this function
 120   * will update an existing instance with new data.
 121   *
 122   * @param stdClass $bigbluebuttonbn An object from the form in mod_form.php
 123   * @return bool Success/Fail
 124   */
 125  function bigbluebuttonbn_update_instance($bigbluebuttonbn) {
 126      global $DB;
 127      // Excecute preprocess.
 128      mod_helper::process_pre_save($bigbluebuttonbn);
 129  
 130      // Pre-set initial values.
 131      $bigbluebuttonbn->id = $bigbluebuttonbn->instance;
 132      $bigbluebuttonbn->presentation = files::save_media_file($bigbluebuttonbn);
 133  
 134      if (empty($bigbluebuttonbn->guestjoinurl) || empty($bigbluebuttonbn->guestpassword)) {
 135          [$bigbluebuttonbn->guestlinkuid, $bigbluebuttonbn->guestpassword] =
 136              \mod_bigbluebuttonbn\plugin::generate_guest_meeting_credentials();
 137      }
 138      // Update a record.
 139      $DB->update_record('bigbluebuttonbn', $bigbluebuttonbn);
 140  
 141      // Get the meetingid column in the bigbluebuttonbn table.
 142      $bigbluebuttonbn->meetingid = (string) $DB->get_field('bigbluebuttonbn', 'meetingid', ['id' => $bigbluebuttonbn->id]);
 143  
 144      // Log update action.
 145      logger::log_instance_updated(instance::get_from_instanceid($bigbluebuttonbn->id));
 146  
 147      // Complete the process.
 148      mod_helper::process_post_save($bigbluebuttonbn);
 149      // Call any active subplugin so to signal update.
 150      extension::update_instance($bigbluebuttonbn);
 151      return true;
 152  }
 153  
 154  /**
 155   * Given an ID of an instance of this module,
 156   * this function will permanently delete the instance
 157   * and any data that depends on it.
 158   *
 159   * @param int $id Id of the module instance
 160   *
 161   * @return bool Success/Failure
 162   */
 163  function bigbluebuttonbn_delete_instance($id) {
 164      global $DB;
 165  
 166      $instance = instance::get_from_instanceid($id);
 167      if (empty($instance)) {
 168          return false;
 169      }
 170      // End all meeting if any still running.
 171      try {
 172          $meeting = new meeting($instance);
 173          $meeting->end_meeting();
 174      } catch (moodle_exception $e) {
 175          // Do not log any issue when testing.
 176          if (!(defined('PHPUNIT_TEST') && PHPUNIT_TEST) && !defined('BEHAT_SITE_RUNNING')) {
 177              debugging($e->getMessage(), DEBUG_DEVELOPER, $e->getTrace());
 178          }
 179      }
 180      // Get all possible groups (course and course module).
 181      $groupids = [];
 182      if (groups_get_activity_groupmode($instance->get_cm())) {
 183          $coursegroups = groups_get_activity_allowed_groups($instance->get_cm());
 184          $groupids = array_map(
 185              function($gp) {
 186                  return $gp->id;
 187              },
 188              $coursegroups);
 189      }
 190      // End all meetings for all groups.
 191      foreach ($groupids as $groupid) {
 192          try {
 193              $instance->set_group_id($groupid);
 194              $meeting = new meeting($instance);
 195              $meeting->end_meeting();
 196          } catch (moodle_exception $e) {
 197              debugging($e->getMessage() . ' for group ' . $groupid, DEBUG_NORMAL, $e->getTrace());
 198          }
 199      }
 200  
 201      $result = true;
 202  
 203      // Call any active subplugin so to signal deletion.
 204      extension::delete_instance($id);
 205  
 206      // Delete the instance.
 207      if (!$DB->delete_records('bigbluebuttonbn', ['id' => $id])) {
 208          $result = false;
 209      }
 210  
 211      // Delete dependant events.
 212      if (!$DB->delete_records('event', ['modulename' => 'bigbluebuttonbn', 'instance' => $id])) {
 213          $result = false;
 214      }
 215  
 216      // Log action performed.
 217      logger::log_instance_deleted($instance);
 218  
 219      // Mark dependent recordings as headless.
 220      foreach (recording::get_records(['bigbluebuttonbnid' => $id]) as $recording) {
 221          $recording->set('headless', recording::RECORDING_HEADLESS);
 222          $recording->update();
 223      }
 224  
 225      return $result;
 226  }
 227  
 228  /**
 229   * Return a small object with summary information about what a
 230   * user has done with a given particular instance of this module
 231   * Used for user activity reports.
 232   *
 233   * @param stdClass $course
 234   * @param stdClass $user
 235   * @param cm_info $mod
 236   * @param stdClass $bigbluebuttonbn
 237   *
 238   * @return stdClass with info and time (timestamp of the last log)
 239   */
 240  function bigbluebuttonbn_user_outline(stdClass $course, stdClass $user, cm_info $mod, stdClass $bigbluebuttonbn): stdClass {
 241      [$infos, $logtimestamps] = \mod_bigbluebuttonbn\local\helpers\user_info::get_user_info_outline($course, $user, $mod);
 242      return (object) [
 243          'info' => join(',', $infos),
 244          'time' => !empty($logtimestamps) ? max($logtimestamps) : 0
 245      ];
 246  }
 247  
 248  /**
 249   * Print a detailed representation of what a user has done with
 250   * a given particular instance of this module, for user activity reports.
 251   *
 252   * @param stdClass $course
 253   * @param stdClass $user
 254   * @param cm_info $mod
 255   * @param stdClass $bigbluebuttonbn
 256   *
 257   */
 258  function bigbluebuttonbn_user_complete(stdClass $course, stdClass $user, cm_info $mod, stdClass $bigbluebuttonbn) {
 259      [$infos] = \mod_bigbluebuttonbn\local\helpers\user_info::get_user_info_outline($course, $user, $mod);
 260      echo join(', ', $infos);
 261  }
 262  
 263  /**
 264   * This flags this module with the capability to override the completion status with the custom completion rules.
 265   *
 266   *
 267   * @return int
 268   */
 269  function bigbluebuttonbn_get_completion_aggregation_state() {
 270      return COMPLETION_CUSTOM_MODULE_FLOW;
 271  }
 272  
 273  /**
 274   * Returns all other caps used in module.
 275   *
 276   * @return string[]
 277   */
 278  function bigbluebuttonbn_get_extra_capabilities() {
 279      return ['moodle/site:accessallgroups'];
 280  }
 281  
 282  /**
 283   * Called by course/reset.php
 284   *
 285   * @param MoodleQuickForm $mform
 286   */
 287  function bigbluebuttonbn_reset_course_form_definition(&$mform) {
 288      $items = reset::reset_course_items();
 289      $mform->addElement('header', 'bigbluebuttonbnheader', get_string('modulenameplural', 'bigbluebuttonbn'));
 290      foreach ($items as $item => $default) {
 291          $mform->addElement(
 292              'advcheckbox',
 293              "reset_bigbluebuttonbn_{$item}",
 294              get_string("reset{$item}", 'bigbluebuttonbn')
 295          );
 296          if ($item == 'logs' || $item == 'recordings') {
 297              $mform->addHelpButton("reset_bigbluebuttonbn_{$item}", "reset{$item}", 'bigbluebuttonbn');
 298          }
 299      }
 300  }
 301  
 302  /**
 303   * Course reset form defaults.
 304   *
 305   * @param stdClass $course
 306   * @return array
 307   */
 308  function bigbluebuttonbn_reset_course_form_defaults(stdClass $course) {
 309      $formdefaults = [];
 310      $items = reset::reset_course_items();
 311      // All unchecked by default.
 312      foreach ($items as $item => $default) {
 313          $formdefaults["reset_bigbluebuttonbn_{$item}"] = $default;
 314      }
 315      return $formdefaults;
 316  }
 317  
 318  /**
 319   * This function is used by the reset_course_userdata function in moodlelib.
 320   *
 321   * @param stdClass $data the data submitted from the reset course.
 322   * @return array status array
 323   */
 324  function bigbluebuttonbn_reset_userdata(stdClass $data) {
 325      $items = reset::reset_course_items();
 326      $status = [];
 327  
 328      // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
 329      // See MDL-9367.
 330      if (array_key_exists('recordings', $items) && !empty($data->reset_bigbluebuttonbn_recordings)) {
 331          // Remove all the recordings from a BBB server that are linked to the room/activities in this course.
 332          reset::reset_recordings($data->courseid);
 333          unset($items['recordings']);
 334          $status[] = reset::reset_getstatus('recordings');
 335      }
 336  
 337      if (!empty($data->reset_bigbluebuttonbn_tags)) {
 338          // Remove all the tags linked to the room/activities in this course.
 339          reset::reset_tags($data->courseid);
 340          unset($items['tags']);
 341          $status[] = reset::reset_getstatus('tags');
 342      }
 343  
 344      if (!empty($data->reset_bigbluebuttonbn_logs)) {
 345          // Remove all the tags linked to the room/activities in this course.
 346          reset::reset_logs($data->courseid);
 347          unset($items['logs']);
 348          $status[] = reset::reset_getstatus('logs');
 349      }
 350      return $status;
 351  }
 352  
 353  /**
 354   * Given a course_module object, this function returns any
 355   * "extra" information that may be needed when printing
 356   * this activity in a course listing.
 357   * See get_array_of_activities() in course/lib.php.
 358   *
 359   * @param stdClass $coursemodule
 360   *
 361   * @return null|cached_cm_info
 362   */
 363  function bigbluebuttonbn_get_coursemodule_info($coursemodule) {
 364      $instance = instance::get_from_instanceid($coursemodule->instance);
 365      if (empty($instance)) {
 366          return null;
 367      }
 368      $info = new cached_cm_info();
 369      // Warning here: if any of the instance method calls ::get_cm this will result is a recursive call.
 370      // So best is just to access instance variables not linked to the cm.
 371      $info->name = $instance->get_instance_var('name');
 372      if ($coursemodule->showdescription) {
 373          // Convert intro to html. Do not filter cached version, filters run at display time.
 374          $info->content = format_module_intro('bigbluebuttonbn', $instance->get_instance_data(), $coursemodule->id, false);
 375      }
 376      $customcompletionfields = custom_completion::get_defined_custom_rules();
 377      // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
 378      if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
 379          foreach ($customcompletionfields as $completiontype) {
 380              $info->customdata['customcompletionrules'][$completiontype] =
 381                  $instance->get_instance_var($completiontype) ?? 0;
 382          }
 383      }
 384  
 385      return $info;
 386  }
 387  
 388  /**
 389   * Serves the bigbluebuttonbn attachments. Implements needed access control ;-).
 390   *
 391   * @param stdClass $course course object
 392   * @param stdClass $cm course module object
 393   * @param context $context context object
 394   * @param string $filearea file area
 395   * @param array $args extra arguments
 396   * @param bool $forcedownload whether or not force download
 397   * @param array $options additional options affecting the file serving
 398   *
 399   * @return false|null false if file not found, does not return if found - justsend the file
 400   * @category files
 401   *
 402   */
 403  function bigbluebuttonbn_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = []) {
 404      if (!files::pluginfile_valid($context, $filearea)) {
 405          return false;
 406      }
 407      $file = files::pluginfile_file($course, $cm, $context, $filearea, $args);
 408      if (empty($file)) {
 409          return false;
 410      }
 411      // Finally send the file.
 412      return send_stored_file($file, 0, 0, $forcedownload, $options); // Download MUST be forced - security!
 413  }
 414  
 415  /**
 416   * Mark the activity completed (if required) and trigger the course_module_viewed event.
 417   *
 418   * @param stdClass $bigbluebuttonbn bigbluebuttonbn object
 419   * @param stdClass $course course object
 420   * @param cm_info $cm course module object
 421   * @param context $context context object
 422   * @since Moodle 3.0
 423   */
 424  function bigbluebuttonbn_view($bigbluebuttonbn, $course, $cm, $context) {
 425  
 426      // Trigger course_module_viewed event.
 427      $params = [
 428          'context' => $context,
 429          'objectid' => $bigbluebuttonbn->id
 430      ];
 431  
 432      $event = \mod_bigbluebuttonbn\event\course_module_viewed::create($params); // Fix event name.
 433      $cmrecord = $cm->get_course_module_record();
 434      $event->add_record_snapshot('course_modules', $cmrecord);
 435      $event->add_record_snapshot('course', $course);
 436      $event->add_record_snapshot('bigbluebuttonbn', $bigbluebuttonbn);
 437      $event->trigger();
 438  
 439      // Completion.
 440      $completion = new completion_info($course);
 441      $completion->set_module_viewed($cm);
 442  }
 443  
 444  /**
 445   * Check if the module has any update that affects the current user since a given time.
 446   *
 447   * @param cm_info $cm course module data
 448   * @param int $from the time to check updates from
 449   * @param array $filter if we need to check only specific updates
 450   * @return stdClass an object with the different type of areas indicating if they were updated or not
 451   * @since Moodle 3.2
 452   */
 453  function bigbluebuttonbn_check_updates_since(cm_info $cm, $from, $filter = []) {
 454      $updates = course_check_module_updates_since($cm, $from, ['content'], $filter);
 455      return $updates;
 456  }
 457  
 458  /**
 459   * This function receives a calendar event and returns the action associated with it, or null if there is none.
 460   *
 461   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
 462   * is not displayed on the block.
 463   *
 464   * @param calendar_event $event
 465   * @param action_factory $factory
 466   * @return action_interface|null
 467   */
 468  function mod_bigbluebuttonbn_core_calendar_provide_event_action(
 469      calendar_event $event,
 470      action_factory $factory
 471  ) {
 472      global $DB;
 473  
 474      $time = time();
 475  
 476      // Get mod info.
 477      $cm = get_fast_modinfo($event->courseid)->instances['bigbluebuttonbn'][$event->instance];
 478  
 479      // Get bigbluebuttonbn activity.
 480      $bigbluebuttonbn = $DB->get_record('bigbluebuttonbn', ['id' => $event->instance], '*', MUST_EXIST);
 481  
 482      // Set flag haspassed if closingtime has already passed only if it is defined.
 483      $haspassed = ($bigbluebuttonbn->closingtime) && $bigbluebuttonbn->closingtime < $time;
 484  
 485      // Set flag hasstarted if startingtime has already passed or not defined.
 486      $hasstarted = $bigbluebuttonbn->openingtime < $time;
 487  
 488      // Return null if it has passed or not started.
 489      if ($haspassed || !$hasstarted) {
 490          return null;
 491      }
 492  
 493      // Get if the user has joined in live session or viewed the recorded.
 494      $customcompletion = new custom_completion($cm, $event->userid);
 495      $usercomplete = $customcompletion->get_overall_completion_state();
 496      $instance = instance::get_from_instanceid($bigbluebuttonbn->id);
 497      // Get if the room is available.
 498      $roomavailable = $instance->is_currently_open();
 499  
 500      $meetinginfo = null;
 501      // Check first if the server can be contacted.
 502      try {
 503          if (empty(bigbluebutton_proxy::get_server_version())) {
 504              // In this case we should already have debugging message printed.
 505              return null;
 506          }
 507          // Get if the user can join.
 508          $meetinginfo = meeting::get_meeting_info_for_instance($instance);
 509      } catch (moodle_exception $e) {
 510          debugging('Error - Cannot retrieve info from meeting ('.$instance->get_meeting_id().') ' . $e->getMessage());
 511          return null;
 512      }
 513      $usercanjoin = $meetinginfo->canjoin;
 514  
 515      // Check if the room is closed and the user has already joined this session or played the record.
 516      if (!$roomavailable && $usercomplete) {
 517          return null;
 518      }
 519  
 520      // Check if the user can join this session.
 521      $actionable = ($roomavailable && $usercanjoin);
 522  
 523      // Action data.
 524      $string = get_string('view_room', 'bigbluebuttonbn');
 525      $url = new moodle_url('/mod/bigbluebuttonbn/view.php', ['id' => $cm->id]);
 526      if (groups_get_activity_groupmode($cm) == NOGROUPS) {
 527          // No groups mode.
 528          $string = get_string('view_conference_action_join', 'bigbluebuttonbn');
 529          $url = new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [
 530                  'action' => 'join',
 531                  'id' => $cm->id,
 532                  'bn' => $bigbluebuttonbn->id,
 533                  'timeline' => 1]
 534          );
 535      }
 536  
 537      return $factory->create_instance($string, $url, 1, $actionable);
 538  }
 539  
 540  /**
 541   * Is the event visible?
 542   *
 543   * @param calendar_event $event
 544   * @return bool Returns true if the event is visible to the current user, false otherwise.
 545   */
 546  function mod_bigbluebuttonbn_core_calendar_is_event_visible(calendar_event $event) {
 547      $instance = instance::get_from_instanceid($event->instance);
 548      if (!$instance) {
 549          return false;
 550      }
 551      $activitystatus = mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy::view_get_activity_status($instance);
 552      return $activitystatus != 'ended';
 553  }
 554  
 555  /**
 556   * Adds module specific settings to the settings block
 557   *
 558   * @param settings_navigation $settingsnav The settings navigation object
 559   * @param navigation_node $nodenav The node to add module settings to
 560   */
 561  function bigbluebuttonbn_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $nodenav) {
 562      global $USER;
 563      // Don't add validate completion if the callback for meetingevents is NOT enabled.
 564      if (!(boolean) \mod_bigbluebuttonbn\local\config::get('meetingevents_enabled')) {
 565          return;
 566      }
 567      // Don't add validate completion if user is not allowed to edit the activity.
 568      $context = context_module::instance($settingsnav->get_page()->cm->id);
 569      if (!has_capability('moodle/course:manageactivities', $context, $USER->id)) {
 570          return;
 571      }
 572      $completionvalidate = '#action=completion_validate&bigbluebuttonbn=' . $settingsnav->get_page()->cm->instance;
 573      $nodenav->add(get_string('completionvalidatestate', 'bigbluebuttonbn'),
 574          $completionvalidate, navigation_node::TYPE_CONTAINER);
 575  }
 576  
 577  /**
 578   * In place editable for the recording table
 579   *
 580   * @param string $itemtype
 581   * @param string $itemid
 582   * @param mixed $newvalue
 583   * @return mixed|null
 584   */
 585  function bigbluebuttonbn_inplace_editable($itemtype, $itemid, $newvalue) {
 586      $editableclass = "\\mod_bigbluebuttonbn\\output\\recording_{$itemtype}_editable";
 587      if (class_exists($editableclass)) {
 588          return call_user_func([$editableclass, 'update'], $itemid, $newvalue);
 589      }
 590      return null; // Will raise an exception in core update_inplace_editable method.
 591  }
 592  
 593  /**
 594   * Returns all events since a given time in specified bigbluebutton activity.
 595   * We focus here on the two events: play and join.
 596   *
 597   * @param array $activities
 598   * @param int $index
 599   * @param int $timestart
 600   * @param int $courseid
 601   * @param int $cmid
 602   * @param int $userid
 603   * @param int $groupid
 604   * @return array
 605   */
 606  function bigbluebuttonbn_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0,
 607      $groupid = 0): array {
 608      $instance = instance::get_from_cmid($cmid);
 609      $instance->set_group_id($groupid);
 610      $cm = $instance->get_cm();
 611      $logs =
 612          logger::get_user_completion_logs_with_userfields($instance,
 613              $userid ?? null,
 614              [logger::EVENT_JOIN, logger::EVENT_PLAYED],
 615              $timestart);
 616  
 617      foreach ($logs as $log) {
 618          $activity = new stdClass();
 619  
 620          $activity->type = 'bigbluebuttonbn';
 621          $activity->cmid = $cm->id;
 622          $activity->name = format_string($instance->get_meeting_name(), true);
 623          $activity->sectionnum = $cm->sectionnum;
 624          $activity->timestamp = $log->timecreated;
 625          $activity->user = new stdClass();
 626          $userfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
 627          foreach ($userfields as $userfield) {
 628              if ($userfield == 'id') {
 629                  // Aliased in SQL above.
 630                  $activity->user->{$userfield} = $log->userid;
 631              } else {
 632                  $activity->user->{$userfield} = $log->{$userfield};
 633              }
 634          }
 635          $activity->user->fullname = fullname($log);
 636          $activity->content = '';
 637          $activity->eventname = logger::get_printable_event_name($log);
 638          if ($log->log == logger::EVENT_PLAYED) {
 639              if (!empty($log->meta)) {
 640                  $meta = json_decode($log->meta);
 641                  if (!empty($meta->recordingid)) {
 642                      $recording = recording::get_record(['id' => $meta->recordingid]);
 643                      if ($recording) {
 644                          $activity->content = $recording->get('name');
 645                      }
 646                  }
 647              }
 648          }
 649          $activities[$index++] = $activity;
 650      }
 651      return $activities;
 652  }
 653  
 654  /**
 655   * Outputs the bigbluebutton logs indicated by $activity.
 656   *
 657   * @param stdClass $activity the activity object the bigbluebuttonbn resides in
 658   * @param int $courseid the id of the course the bigbluebuttonbn resides in
 659   * @param bool $detail not used, but required for compatibilty with other modules
 660   * @param array $modnames not used, but required for compatibilty with other modules
 661   * @param bool $viewfullnames not used, but required for compatibilty with other modules
 662   */
 663  function bigbluebuttonbn_print_recent_mod_activity(stdClass $activity, int $courseid, bool $detail, array $modnames,
 664      bool $viewfullnames) {
 665      global $OUTPUT;
 666      $modinfo = [];
 667      $userpicture = $OUTPUT->user_picture($activity->user);
 668  
 669      $template = ['userpicture' => $userpicture,
 670          'submissiontimestamp' => $activity->timestamp,
 671          'modinfo' => $modinfo,
 672          'userurl' => new moodle_url('/user/view.php', array('id' => $activity->user->id, 'course' => $courseid)),
 673          'fullname' => $activity->user->fullname];
 674      if (isset($activity->eventname)) {
 675          $template['eventname'] = $activity->eventname;
 676      }
 677      echo $OUTPUT->render_from_template('mod_bigbluebuttonbn/recentactivity', $template);
 678  }
 679  
 680  /**
 681   * Given a course and a date, prints a summary of all the activity for this module
 682   *
 683   * @param object $course
 684   * @param bool $viewfullnames capability
 685   * @param int $timestart
 686   * @return bool success
 687   */
 688  function bigbluebuttonbn_print_recent_activity(object $course, bool $viewfullnames, int $timestart): bool {
 689      global $OUTPUT;
 690      $modinfo = get_fast_modinfo($course);
 691      if (empty($modinfo->instances['bigbluebuttonbn'])) {
 692          return true;
 693      }
 694      $out = '';
 695      foreach ($modinfo->instances['bigbluebuttonbn'] as $cm) {
 696          if (!$cm->uservisible) {
 697              continue;
 698          }
 699          $instance = instance::get_from_cmid($cm->id);
 700          $logs = logger::get_user_completion_logs_with_userfields($instance,
 701              null,
 702              [logger::EVENT_JOIN, logger::EVENT_PLAYED],
 703              $timestart);
 704          if ($logs) {
 705              echo $OUTPUT->heading(get_string('new_bigblubuttonbn_activities', 'bigbluebuttonbn') . ':', 6);
 706              foreach ($logs as $log) {
 707                  $activityurl = new moodle_url('/mod/bigbluebuttonbn/index.php', ['id' => $course->id]);
 708                  print_recent_activity_note($log->timecreated,
 709                      $log,
 710                      logger::get_printable_event_name($log) . ' - ' . $instance->get_meeting_name(),
 711                      $activityurl->out(),
 712                      false,
 713                      $viewfullnames);
 714              }
 715          }
 716  
 717          echo $out;
 718      }
 719      return true;
 720  }
 721  
 722  /**
 723   * Callback method executed prior to enabling the activity module.
 724   *
 725   * @return bool Whether to proceed and enable the plugin or not.
 726   */
 727  function bigbluebuttonbn_pre_enable_plugin_actions(): bool {
 728      global $PAGE;
 729  
 730      // If the default server configuration is used and the administrator has not accepted the default data processing
 731      // agreement, do not enable the plugin. Instead, display a dynamic form where the administrator can confirm that he
 732      // accepts the DPA prior to enabling the plugin.
 733      if (config::get('server_url') === config::DEFAULT_SERVER_URL && !config::get('default_dpa_accepted')) {
 734          $url = new moodle_url('/admin/category.php', ['category' => 'modbigbluebuttonbnfolder']);
 735          \core\notification::add(
 736              get_string('dpainfonotsigned', 'mod_bigbluebuttonbn', $url->out(false)),
 737              \core\notification::ERROR
 738          );
 739          return false;
 740      }
 741      // Otherwise, continue and enable the plugin.
 742      return true;
 743  }
 744  
 745  /**
 746   * Creates a number of BigblueButtonBN activities.
 747   *
 748   * @param tool_generator_course_backend $backend
 749   * @param testing_data_generator $generator
 750   * @param int $courseid
 751   * @param int $number
 752   * @return void
 753   */
 754  function bigbluebuttonbn_course_backend_generator_create_activity(tool_generator_course_backend $backend,
 755      testing_data_generator $generator,
 756      int $courseid,
 757      int $number
 758  ) {
 759      // Set up generator.
 760      $bbbgenerator = $generator->get_plugin_generator('mod_bigbluebuttonbn');
 761  
 762      // Create assignments.
 763      $backend->log('createbigbluebuttonbn', $number, true, 'mod_bigbluebuttonbn');
 764      for ($i = 0; $i < $number; $i++) {
 765          $record = array('course' => $courseid);
 766          $options = array('section' => $backend->get_target_section());
 767          $bbbgenerator->create_instance($record, $options);
 768          $backend->dot($i, $number);
 769      }
 770      $backend->end_log();
 771  }