Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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