Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}