See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body