Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace mod_bigbluebuttonbn;
  18  
  19  use mod_bigbluebuttonbn\event\events;
  20  use stdClass;
  21  
  22  /**
  23   * Utility class for all logs routines helper.
  24   *
  25   * @package   mod_bigbluebuttonbn
  26   * @copyright 2021 onwards, Blindside Networks Inc
  27   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   * @author    Laurent David  (laurent [at] call-learning [dt] fr)
  29   */
  30  class logger {
  31  
  32      /** @var string The bigbluebuttonbn Add event */
  33      public const EVENT_ADD = 'Add';
  34  
  35      /** @var string The bigbluebuttonbn Edit event */
  36      public const EVENT_EDIT = 'Edit';
  37  
  38      /** @var string The bigbluebuttonbn Create event */
  39      public const EVENT_CREATE = 'Create';
  40  
  41      /** @var string The bigbluebuttonbn Join event */
  42      public const EVENT_JOIN = 'Join';
  43  
  44      /** @var string The bigbluebuttonbn Playback event */
  45      public const EVENT_PLAYED = 'Played';
  46  
  47      /** @var string The bigbluebuttonbn Logout event */
  48      public const EVENT_LOGOUT = 'Logout';
  49  
  50      /** @var string The bigbluebuttonbn Import event */
  51      public const EVENT_IMPORT = 'Import';
  52  
  53      /** @var string The bigbluebuttonbn Delete event */
  54      public const EVENT_DELETE = 'Delete';
  55  
  56      /** @var string The bigbluebuttonbn Callback event */
  57      public const EVENT_CALLBACK = 'Callback';
  58  
  59      /** @var string The bigbluebuttonbn Summary event */
  60      public const EVENT_SUMMARY = 'Summary';
  61  
  62      /** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4
  63       *
  64       * Note: Migrated event name change: once a log has been migrated we mark
  65       * it as migrated by changing its log name. This will help to recover
  66       * manually if we have an issue in the migration process.
  67       */
  68      public const EVENT_IMPORT_MIGRATED = 'import-migrated';
  69  
  70      /** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4 */
  71      public const EVENT_CREATE_MIGRATED = 'create-migrated';
  72  
  73      /** @var string The bigbluebuttonbn meeting_start event */
  74      public const EVENT_MEETING_START = 'meeting_start';
  75  
  76      /** @var int The user accessed the session from activity page */
  77      public const ORIGIN_BASE = 0;
  78  
  79      /** @var int The user accessed the session from Timeline */
  80      public const ORIGIN_TIMELINE = 1;
  81  
  82      /** @var int The user accessed the session from Index */
  83      public const ORIGIN_INDEX = 2;
  84  
  85      /**
  86       * Get the user event logs related to completion, for the specified user in the named instance.
  87       *
  88       * @param instance $instance
  89       * @param int|null $userid
  90       * @param array|null $filters
  91       * @param int|null $timestart
  92       * @return array
  93       */
  94      public static function get_user_completion_logs(
  95          instance $instance,
  96          ?int $userid,
  97          ?array $filters,
  98          ?int $timestart = 0
  99      ): array {
 100          global $DB;
 101          $filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
 102          [$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);
 103          return $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, $params);
 104      }
 105  
 106      /**
 107       * Get the user event logs related to completion, for the specified user in the named instance.
 108       *
 109       * @param instance $instance
 110       * @param int|null $userid
 111       * @param array|null $filters
 112       * @param int|null $timestart
 113       * @return array
 114       */
 115      public static function get_user_completion_logs_with_userfields(
 116          instance $instance,
 117          ?int $userid,
 118          ?array $filters,
 119          ?int $timestart = 0
 120      ): array {
 121          global $DB;
 122          $filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
 123          [$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart, 'l');
 124          $userfieldsapi = \core_user\fields::for_userpic();
 125          $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
 126          $logtable = new \core\dml\table('bigbluebuttonbn_logs', 'l', '');
 127          $logtableselect = $logtable->get_field_select();
 128          $logtablefrom = $logtable->get_from_sql();
 129          $usertable = new \core\dml\table('user', 'u', '');
 130          $usertablefrom = $usertable->get_from_sql();
 131          $sql = <<<EOF
 132              SELECT {$logtableselect}, {$userfields}
 133                FROM {$logtablefrom}
 134          INNER JOIN {$usertablefrom} ON u.id = l.userid
 135               WHERE $wheresql
 136  EOF;
 137          return $DB->get_records_sql($sql, $params);
 138      }
 139  
 140      /**
 141       * Get the latest timestamp for any event logs related to completion, for the specified user in the named instance.
 142       *
 143       * @param instance $instance
 144       * @param int|null $userid
 145       * @param array|null $filters
 146       * @param int|null $timestart
 147       * @return int
 148       */
 149      public static function get_user_completion_logs_max_timestamp(
 150          instance $instance,
 151          ?int $userid,
 152          ?array $filters,
 153          ?int $timestart = 0
 154      ): int {
 155          global $DB;
 156  
 157          [$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);
 158          $select = "SELECT MAX(timecreated) ";
 159          $lastlogtime = $DB->get_field_sql($select . ' FROM {bigbluebuttonbn_logs} WHERE ' . $wheresql, $params);
 160          return $lastlogtime ?? 0;
 161      }
 162  
 163      /**
 164       * Helper method to get the right SQL query for completion
 165       *
 166       * @param instance $instance
 167       * @param int|null $userid
 168       * @param array|null $filters
 169       * @param int|null $timestart
 170       * @param string|null $logtablealias
 171       * @return array
 172       */
 173      protected static function get_user_completion_sql_params(instance $instance, ?int $userid, ?array $filters, ?int $timestart,
 174          ?string $logtablealias = null) {
 175          global $DB;
 176          $filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
 177          [$insql, $params] = $DB->get_in_or_equal($filters, SQL_PARAMS_NAMED);
 178          $wheres = [];
 179          $wheres['bigbluebuttonbnid'] = '= :instanceid';
 180          $wheres['courseid'] = '= :courseid'; // This speeds up the requests masively as courseid is an index.
 181          if ($timestart) {
 182              $wheres['timecreated'] = ' > :timestart';
 183              $params['timestart'] = $timestart;
 184          }
 185          if ($userid) {
 186              $wheres['userid'] = ' = :userid';
 187              $params['userid'] = $userid;
 188          }
 189          $params['instanceid'] = $instance->get_instance_id();
 190          $params['courseid'] = $instance->get_course_id();
 191          $wheres['log'] = " $insql";
 192          $wheresqls = [];
 193          foreach ($wheres as $key => $val) {
 194              $prefix = !empty($logtablealias) ? "$logtablealias." : "";
 195              $wheresqls[] = "$prefix$key $val";
 196          }
 197          return [join(' AND ', $wheresqls), $params];
 198      }
 199  
 200      /**
 201       * Log that an instance was created.
 202       *
 203       * Note: This event cannot take the instance class as it is typically called before the cm has been configured.
 204       *
 205       * @param stdClass $instancedata
 206       */
 207      public static function log_instance_created(stdClass $instancedata): void {
 208          self::raw_log(
 209              self::EVENT_ADD,
 210              $instancedata->id,
 211              $instancedata->course,
 212              $instancedata->meetingid
 213          );
 214      }
 215  
 216      /**
 217       * Log that an instance was updated.
 218       *
 219       * @param instance $instance
 220       */
 221      public static function log_instance_updated(instance $instance): void {
 222          self::log($instance, self::EVENT_EDIT);
 223      }
 224  
 225      /**
 226       * Log an instance deleted event.
 227       *
 228       * @param instance $instance
 229       */
 230      public static function log_instance_deleted(instance $instance): void {
 231          global $DB;
 232  
 233          $wheresql = 'bigbluebuttonbnid = :instanceid AND log = :logtype AND ' . $DB->sql_compare_text('meta') . ' = :meta';
 234          $logs = $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, [
 235              'instanceid' => $instance->get_instance_id(),
 236              'logtype' => self::EVENT_CREATE,
 237              'meta' => "{\"record\":true}"
 238          ]);
 239  
 240          $meta = "{\"has_recordings\":" . empty($logs) ? "true" : "false" . "}";
 241          self::log($instance, self::EVENT_DELETE, [], $meta);
 242      }
 243  
 244      /**
 245       * Log an event callback.
 246       *
 247       * @param instance $instance
 248       * @param array $overrides
 249       * @param array $meta
 250       * @return int The new count of callback events
 251       */
 252      public static function log_event_callback(instance $instance, array $overrides, array $meta): int {
 253          self::log(
 254              $instance,
 255              self::EVENT_CALLBACK,
 256              $overrides,
 257              json_encode($meta)
 258          );
 259  
 260          return self::count_callback_events($meta['internalmeetingid'], 'meeting_events');
 261      }
 262  
 263      /**
 264       * Log an event summary event.
 265       *
 266       * @param instance $instance
 267       * @param array $overrides
 268       * @param array $meta
 269       */
 270      public static function log_event_summary(instance $instance, array $overrides = [], array $meta = []): void {
 271          self::log(
 272              $instance,
 273              self::EVENT_SUMMARY,
 274              $overrides,
 275              json_encode($meta)
 276          );
 277      }
 278  
 279      /**
 280       * Log that an instance was viewed.
 281       *
 282       * @param instance $instance
 283       */
 284      public static function log_instance_viewed(instance $instance): void {
 285          self::log_moodle_event($instance, events::$events['view']);
 286      }
 287  
 288      /**
 289       * Log the events for when a meeting was ended.
 290       *
 291       * @param instance $instance
 292       */
 293      public static function log_meeting_ended_event(instance $instance): void {
 294          // Moodle event logger: Create an event for meeting ended.
 295          self::log_moodle_event($instance, events::$events['meeting_end']);
 296  
 297      }
 298  
 299      /**
 300       * Log the relevant events for when a meeting was joined.
 301       *
 302       * @param instance $instance
 303       * @param int $origin
 304       */
 305      public static function log_meeting_joined_event(instance $instance, int $origin): void {
 306          // Moodle event logger: Create an event for meeting joined.
 307          self::log_moodle_event($instance, events::$events['meeting_join']);
 308  
 309          // Internal logger: Instert a record with the meeting created.
 310          self::log(
 311              $instance,
 312              self::EVENT_JOIN,
 313              ['meetingid' => $instance->get_meeting_id()],
 314              json_encode((object) ['origin' => $origin])
 315          );
 316      }
 317  
 318      /**
 319       * Log the relevant events for when a user left a meeting.
 320       *
 321       * @param instance $instance
 322       */
 323      public static function log_meeting_left_event(instance $instance): void {
 324          // Moodle event logger: Create an event for meeting left.
 325          self::log_moodle_event($instance, events::$events['meeting_left']);
 326      }
 327  
 328      /**
 329       * Log the relevant events for when a recording has been played.
 330       *
 331       * @param instance $instance
 332       * @param int $rid RecordID
 333       */
 334      public static function log_recording_played_event(instance $instance, int $rid): void {
 335          // Moodle event logger: Create an event for recording played.
 336          self::log_moodle_event($instance, events::$events['recording_play'], ['other' => $rid]);
 337  
 338          // Internal logger: Insert a record with the playback played.
 339          self::log(
 340              $instance,
 341              self::EVENT_PLAYED,
 342              [
 343                  'meetingid' => $instance->get_meeting_id(),
 344              ],
 345              json_encode(['recordingid' => $rid])
 346          );
 347      }
 348  
 349      /**
 350       * Register a bigbluebuttonbn event from an instance.
 351       *
 352       * @param instance $instance
 353       * @param string $event
 354       * @param array $overrides
 355       * @param string|null $meta
 356       * @return bool
 357       */
 358      protected static function log(instance $instance, string $event, array $overrides = [], ?string $meta = null): bool {
 359          return self::raw_log(
 360              $event,
 361              $instance->get_instance_id(),
 362              $instance->get_course_id(),
 363              $instance->get_meeting_id(),
 364              $overrides,
 365              $meta
 366          );
 367      }
 368  
 369      /**
 370       * Register a bigbluebuttonbn event from raw data.
 371       *
 372       * @param string $event
 373       * @param int $instanceid
 374       * @param int $courseid
 375       * @param string $meetingid
 376       * @param array $overrides
 377       * @param string|null $meta
 378       * @return bool
 379       */
 380      protected static function raw_log(
 381          string $event,
 382          int $instanceid,
 383          int $courseid,
 384          string $meetingid,
 385          array $overrides = [],
 386          ?string $meta = null
 387      ): bool {
 388          global $DB, $USER;
 389  
 390          $log = (object) array_merge([
 391              // Default values.
 392              'courseid' => $courseid,
 393              'bigbluebuttonbnid' => $instanceid,
 394              'userid' => $USER->id,
 395              'meetingid' => $meetingid,
 396              'timecreated' => time(),
 397              'log' => $event,
 398              'meta' => $meta,
 399          ], $overrides);
 400  
 401          return !!$DB->insert_record('bigbluebuttonbn_logs', $log);
 402      }
 403  
 404      /**
 405       * Helper register a bigbluebuttonbn event.
 406       *
 407       * @param instance $instance
 408       * @param string $type
 409       * @param array $options [timecreated, userid, other]
 410       */
 411      protected static function log_moodle_event(instance $instance, string $type, array $options = []): void {
 412          if (!in_array($type, \mod_bigbluebuttonbn\event\events::$events)) {
 413              // No log will be created.
 414              return;
 415          }
 416  
 417          $params = [
 418              'context' => $instance->get_context(),
 419              'objectid' => $instance->get_instance_id(),
 420          ];
 421  
 422          if (array_key_exists('timecreated', $options)) {
 423              $params['timecreated'] = $options['timecreated'];
 424          }
 425  
 426          if (array_key_exists('userid', $options)) {
 427              $params['userid'] = $options['userid'];
 428          }
 429  
 430          if (array_key_exists('other', $options)) {
 431              $params['other'] = $options['other'];
 432          }
 433  
 434          $event = call_user_func_array("\\mod_bigbluebuttonbn\\event\\{$type}::create", [$params]);
 435          $event->add_record_snapshot('course_modules', $instance->get_cm());
 436          $event->add_record_snapshot('course', $instance->get_course());
 437          $event->add_record_snapshot('bigbluebuttonbn', $instance->get_instance_data());
 438          $event->trigger();
 439      }
 440  
 441      /**
 442       * Helper function to count the number of callback logs matching the supplied specifications.
 443       *
 444       * @param string $id
 445       * @param string $callbacktype
 446       * @return int
 447       */
 448      protected static function count_callback_events(string $id, string $callbacktype = 'recording_ready'): int {
 449          global $DB;
 450          // Look for a log record that is of "Callback" type and is related to the given event.
 451          $conditions = [
 452              "log = :logtype",
 453              $DB->sql_like('meta', ':cbtypelike')
 454          ];
 455  
 456          $params = [
 457              'logtype' => self::EVENT_CALLBACK,
 458              'cbtypelike' => "%meeting_events%" // All callbacks are meeting events, even recording events.
 459          ];
 460  
 461          $basesql = 'SELECT COUNT(DISTINCT id) FROM {bigbluebuttonbn_logs}';
 462          switch ($callbacktype) {
 463              case 'recording_ready':
 464                  $conditions[] = $DB->sql_like('meta', ':isrecordid');
 465                  $params['isrecordid'] = '%recordid%'; // The recordid field in the meta field (json encoded).
 466                  break;
 467              case 'meeting_events':
 468                  $conditions[] = $DB->sql_like('meta', ':idlike');
 469                  $params['idlike'] = "%$id%"; // The unique id of the meeting is the meta field (json encoded).
 470                  break;
 471          }
 472          $wheresql = join(' AND ', $conditions);
 473          return $DB->count_records_sql($basesql . ' WHERE ' . $wheresql, $params);
 474      }
 475  
 476      /**
 477       * Log event to string that can be internationalised via get_string.
 478       */
 479      const LOG_TO_STRING = [
 480          self::EVENT_JOIN => 'event_meeting_joined',
 481          self::EVENT_PLAYED => 'event_recording_viewed',
 482          self::EVENT_IMPORT => 'event_recording_imported',
 483          self::EVENT_ADD => 'event_activity_created',
 484          self::EVENT_DELETE => 'event_activity_deleted',
 485          self::EVENT_EDIT => 'event_activity_updated',
 486          self::EVENT_SUMMARY => 'event_meeting_summary',
 487          self::EVENT_LOGOUT => 'event_meeting_left',
 488          self::EVENT_MEETING_START => 'event_meeting_joined',
 489      ];
 490  
 491      /**
 492       * Get the event name (human friendly version)
 493       *
 494       * @param object $log object as returned by get_user_completion_logs_with_userfields
 495       */
 496      public static function get_printable_event_name(object $log) {
 497          $logstringname = self::LOG_TO_STRING[$log->log] ?? 'event_unknown';
 498          return get_string($logstringname, 'mod_bigbluebuttonbn');
 499      }
 500  }