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.

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