AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace mod_bigbluebuttonbn;use mod_bigbluebuttonbn\event\events;use stdClass;/*** Utility class for all logs routines helper.** @package mod_bigbluebuttonbn* @copyright 2021 onwards, Blindside Networks Inc* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later* @author Laurent David (laurent [at] call-learning [dt] fr)*/class logger {/** @var string The bigbluebuttonbn Add event */public const EVENT_ADD = 'Add';/** @var string The bigbluebuttonbn Edit event */public const EVENT_EDIT = 'Edit';/** @var string The bigbluebuttonbn Create event */public const EVENT_CREATE = 'Create';/** @var string The bigbluebuttonbn Join event */public const EVENT_JOIN = 'Join';/** @var string The bigbluebuttonbn Playback event */public const EVENT_PLAYED = 'Played';/** @var string The bigbluebuttonbn Logout event */public const EVENT_LOGOUT = 'Logout';/** @var string The bigbluebuttonbn Import event */public const EVENT_IMPORT = 'Import';/** @var string The bigbluebuttonbn Delete event */public const EVENT_DELETE = 'Delete';/** @var string The bigbluebuttonbn Callback event */public const EVENT_CALLBACK = 'Callback';/** @var string The bigbluebuttonbn Summary event */public const EVENT_SUMMARY = 'Summary';/** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4** Note: Migrated event name change: once a log has been migrated we mark* it as migrated by changing its log name. This will help to recover* manually if we have an issue in the migration process.*/public const EVENT_IMPORT_MIGRATED = 'import-migrated';/** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4 */public const EVENT_CREATE_MIGRATED = 'create-migrated';/** @var string The bigbluebuttonbn meeting_start event */public const EVENT_MEETING_START = 'meeting_start';/** @var int The user accessed the session from activity page */public const ORIGIN_BASE = 0;/** @var int The user accessed the session from Timeline */public const ORIGIN_TIMELINE = 1;/** @var int The user accessed the session from Index */public const ORIGIN_INDEX = 2;/*** Get the user event logs related to completion, for the specified user in the named instance.** @param instance $instance* @param int|null $userid* @param array|null $filters* @param int|null $timestart* @return array*/public static function get_user_completion_logs(instance $instance,?int $userid,?array $filters,?int $timestart = 0): array {global $DB;$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);return $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, $params);}/*** Get the user event logs related to completion, for the specified user in the named instance.** @param instance $instance* @param int|null $userid* @param array|null $filters* @param int|null $timestart* @return array*/public static function get_user_completion_logs_with_userfields(instance $instance,?int $userid,?array $filters,?int $timestart = 0): array {global $DB;$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart, 'l');$userfieldsapi = \core_user\fields::for_userpic();$userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;$logtable = new \core\dml\table('bigbluebuttonbn_logs', 'l', '');$logtableselect = $logtable->get_field_select();$logtablefrom = $logtable->get_from_sql();$usertable = new \core\dml\table('user', 'u', '');$usertablefrom = $usertable->get_from_sql();$sql = <<<EOFSELECT {$logtableselect}, {$userfields}FROM {$logtablefrom}INNER JOIN {$usertablefrom} ON u.id = l.useridWHERE $wheresqlEOF;return $DB->get_records_sql($sql, $params);}/*** Get the latest timestamp for any event logs related to completion, for the specified user in the named instance.** @param instance $instance* @param int|null $userid* @param array|null $filters* @param int|null $timestart* @return int*/public static function get_user_completion_logs_max_timestamp(instance $instance,?int $userid,?array $filters,?int $timestart = 0): int {global $DB;[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);$select = "SELECT MAX(timecreated) ";$lastlogtime = $DB->get_field_sql($select . ' FROM {bigbluebuttonbn_logs} WHERE ' . $wheresql, $params);return $lastlogtime ?? 0;}/*** Helper method to get the right SQL query for completion** @param instance $instance* @param int|null $userid* @param array|null $filters* @param int|null $timestart* @param string|null $logtablealias* @return array*/protected static function get_user_completion_sql_params(instance $instance, ?int $userid, ?array $filters, ?int $timestart,?string $logtablealias = null) {global $DB;$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];[$insql, $params] = $DB->get_in_or_equal($filters, SQL_PARAMS_NAMED);$wheres = [];$wheres['bigbluebuttonbnid'] = '= :instanceid';$wheres['courseid'] = '= :courseid'; // This speeds up the requests masively as courseid is an index.if ($timestart) {$wheres['timecreated'] = ' > :timestart';$params['timestart'] = $timestart;}if ($userid) {$wheres['userid'] = ' = :userid';$params['userid'] = $userid;}$params['instanceid'] = $instance->get_instance_id();$params['courseid'] = $instance->get_course_id();$wheres['log'] = " $insql";$wheresqls = [];foreach ($wheres as $key => $val) {$prefix = !empty($logtablealias) ? "$logtablealias." : "";$wheresqls[] = "$prefix$key $val";}return [join(' AND ', $wheresqls), $params];}/*** Log that an instance was created.** Note: This event cannot take the instance class as it is typically called before the cm has been configured.** @param stdClass $instancedata*/public static function log_instance_created(stdClass $instancedata): void {self::raw_log(self::EVENT_ADD,$instancedata->id,$instancedata->course,$instancedata->meetingid);}/*** Log that an instance was updated.** @param instance $instance*/public static function log_instance_updated(instance $instance): void {self::log($instance, self::EVENT_EDIT);}/*** Log an instance deleted event.** @param instance $instance*/public static function log_instance_deleted(instance $instance): void {global $DB;$wheresql = 'bigbluebuttonbnid = :instanceid AND log = :logtype AND ' . $DB->sql_compare_text('meta') . ' = :meta';$logs = $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, ['instanceid' => $instance->get_instance_id(),'logtype' => self::EVENT_CREATE,'meta' => "{\"record\":true}"]);$meta = "{\"has_recordings\":" . empty($logs) ? "true" : "false" . "}";self::log($instance, self::EVENT_DELETE, [], $meta);}/*** Log an event callback.** @param instance $instance* @param array $overrides* @param array $meta* @return int The new count of callback events*/public static function log_event_callback(instance $instance, array $overrides, array $meta): int {self::log($instance,self::EVENT_CALLBACK,$overrides,json_encode($meta));return self::count_callback_events($meta['internalmeetingid'], 'meeting_events');}/*** Log an event summary event.** @param instance $instance* @param array $overrides* @param array $meta*/public static function log_event_summary(instance $instance, array $overrides = [], array $meta = []): void {self::log($instance,self::EVENT_SUMMARY,$overrides,json_encode($meta));}/*** Log that an instance was viewed.** @param instance $instance*/public static function log_instance_viewed(instance $instance): void {self::log_moodle_event($instance, events::$events['view']);}/*** Log the events for when a meeting was ended.** @param instance $instance*/public static function log_meeting_ended_event(instance $instance): void {// Moodle event logger: Create an event for meeting ended.self::log_moodle_event($instance, events::$events['meeting_end']);}/*** Log the relevant events for when a meeting was joined.** @param instance $instance* @param int $origin*/public static function log_meeting_joined_event(instance $instance, int $origin): void {// Moodle event logger: Create an event for meeting joined.self::log_moodle_event($instance, events::$events['meeting_join']);// Internal logger: Instert a record with the meeting created.self::log($instance,self::EVENT_JOIN,['meetingid' => $instance->get_meeting_id()],json_encode((object) ['origin' => $origin]));}/*** Log the relevant events for when a user left a meeting.** @param instance $instance*/public static function log_meeting_left_event(instance $instance): void {// Moodle event logger: Create an event for meeting left.self::log_moodle_event($instance, events::$events['meeting_left']);}/*** Log the relevant events for when a recording has been played.** @param instance $instance* @param int $rid RecordID*/public static function log_recording_played_event(instance $instance, int $rid): void {// Moodle event logger: Create an event for recording played.self::log_moodle_event($instance, events::$events['recording_play'], ['other' => $rid]);// Internal logger: Insert a record with the playback played.self::log($instance,self::EVENT_PLAYED,['meetingid' => $instance->get_meeting_id(),],json_encode(['recordingid' => $rid]));}/*** Register a bigbluebuttonbn event from an instance.** @param instance $instance* @param string $event* @param array $overrides* @param string|null $meta* @return bool*/protected static function log(instance $instance, string $event, array $overrides = [], ?string $meta = null): bool {return self::raw_log($event,$instance->get_instance_id(),$instance->get_course_id(),$instance->get_meeting_id(),$overrides,$meta);}/*** Register a bigbluebuttonbn event from raw data.** @param string $event* @param int $instanceid* @param int $courseid* @param string $meetingid* @param array $overrides* @param string|null $meta* @return bool*/protected static function raw_log(string $event,int $instanceid,int $courseid,string $meetingid,array $overrides = [],?string $meta = null): bool {global $DB, $USER;$log = (object) array_merge([// Default values.'courseid' => $courseid,'bigbluebuttonbnid' => $instanceid,'userid' => $USER->id,'meetingid' => $meetingid,'timecreated' => time(),'log' => $event,'meta' => $meta,], $overrides);return !!$DB->insert_record('bigbluebuttonbn_logs', $log);}/*** Helper register a bigbluebuttonbn event.** @param instance $instance* @param string $type* @param array $options [timecreated, userid, other]*/protected static function log_moodle_event(instance $instance, string $type, array $options = []): void {if (!in_array($type, \mod_bigbluebuttonbn\event\events::$events)) {// No log will be created.return;}$params = ['context' => $instance->get_context(),'objectid' => $instance->get_instance_id(),];if (array_key_exists('timecreated', $options)) {$params['timecreated'] = $options['timecreated'];}if (array_key_exists('userid', $options)) {$params['userid'] = $options['userid'];}if (array_key_exists('other', $options)) {$params['other'] = $options['other'];}$event = call_user_func_array("\\mod_bigbluebuttonbn\\event\\{$type}::create", [$params]);$event->add_record_snapshot('course_modules', $instance->get_cm());$event->add_record_snapshot('course', $instance->get_course());$event->add_record_snapshot('bigbluebuttonbn', $instance->get_instance_data());$event->trigger();}/*** Helper function to count the number of callback logs matching the supplied specifications.** @param string $id* @param string $callbacktype* @return int*/protected static function count_callback_events(string $id, string $callbacktype = 'recording_ready'): int {global $DB;// Look for a log record that is of "Callback" type and is related to the given event.$conditions = ["log = :logtype",$DB->sql_like('meta', ':cbtypelike')];$params = ['logtype' => self::EVENT_CALLBACK,'cbtypelike' => "%meeting_events%" // All callbacks are meeting events, even recording events.];$basesql = 'SELECT COUNT(DISTINCT id) FROM {bigbluebuttonbn_logs}';switch ($callbacktype) {case 'recording_ready':$conditions[] = $DB->sql_like('meta', ':isrecordid');$params['isrecordid'] = '%recordid%'; // The recordid field in the meta field (json encoded).break;case 'meeting_events':$conditions[] = $DB->sql_like('meta', ':idlike');$params['idlike'] = "%$id%"; // The unique id of the meeting is the meta field (json encoded).break;}$wheresql = join(' AND ', $conditions);return $DB->count_records_sql($basesql . ' WHERE ' . $wheresql, $params);}/*** Log event to string that can be internationalised via get_string.*/const LOG_TO_STRING = [self::EVENT_JOIN => 'event_meeting_joined',self::EVENT_PLAYED => 'event_recording_viewed',self::EVENT_IMPORT => 'event_recording_imported',self::EVENT_ADD => 'event_activity_created',self::EVENT_DELETE => 'event_activity_deleted',self::EVENT_EDIT => 'event_activity_updated',self::EVENT_SUMMARY => 'event_meeting_summary',self::EVENT_LOGOUT => 'event_meeting_left',self::EVENT_MEETING_START => 'event_meeting_joined',];/*** Get the event name (human friendly version)** @param object $log object as returned by get_user_completion_logs_with_userfields*/public static function get_printable_event_name(object $log) {$logstringname = self::LOG_TO_STRING[$log->log] ?? 'event_unknown';return get_string($logstringname, 'mod_bigbluebuttonbn');}}