Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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 cache;
20
use context;
21
use context_course;
22
use context_module;
23
use core\persistent;
24
use mod_bigbluebuttonbn\local\proxy\recording_proxy;
25
use moodle_url;
26
use stdClass;
27
 
28
/**
29
 * The recording entity.
30
 *
31
 * This is utility class that defines a single recording, and provides methods for their local handling locally, and
32
 * communication with the bigbluebutton server.
33
 *
34
 * @package mod_bigbluebuttonbn
35
 * @copyright 2021 onwards, Blindside Networks Inc
36
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 */
38
class recording extends persistent {
39
    /** The table name. */
40
    const TABLE = 'bigbluebuttonbn_recordings';
41
 
42
    /** @var int Defines that the activity used to create the recording no longer exists */
43
    public const RECORDING_HEADLESS = 1;
44
 
45
    /** @var int Defines that the recording is not the original but an imported one */
46
    public const RECORDING_IMPORTED = 1;
47
 
48
    /** @var int Defines that the list should include imported recordings */
49
    public const INCLUDE_IMPORTED_RECORDINGS = true;
50
 
51
    /** @var int A meeting set to be recorded still awaits for a recording update */
52
    public const RECORDING_STATUS_AWAITING = 0;
53
 
54
    /** @var int A meeting set to be recorded was not recorded and dismissed by BBB */
55
    public const RECORDING_STATUS_DISMISSED = 1;
56
 
57
    /** @var int A meeting set to be recorded has a recording processed */
58
    public const RECORDING_STATUS_PROCESSED = 2;
59
 
60
    /** @var int A meeting set to be recorded received notification callback from BBB */
61
    public const RECORDING_STATUS_NOTIFIED = 3;
62
 
63
    /** @var int A meeting set to be recorded was processed and set back to an awaiting state */
64
    public const RECORDING_STATUS_RESET = 4;
65
 
66
    /** @var int A meeting set to be recorded was deleted from bigbluebutton */
67
    public const RECORDING_STATUS_DELETED = 5;
68
 
69
    /** @var bool Whether metadata been changed so the remote information needs to be updated ? */
70
    protected $metadatachanged = false;
71
 
72
    /** @var int A refresh period for recordings, defaults to 300s (5mins) */
73
    public const RECORDING_REFRESH_DEFAULT_PERIOD = 300;
74
 
75
    /** @var int A time limit for recordings to be dismissed, defaults to 30d (30days) */
76
    public const RECORDING_TIME_LIMIT_DAYS = 30;
77
 
78
    /** @var array A cached copy of the metadata */
79
    protected $metadata = null;
80
 
81
    /** @var instance A cached copy of the instance */
82
    protected $instance;
83
 
84
    /** @var bool imported recording status */
85
    public $imported;
86
 
87
    /**
88
     * Create an instance of this class.
89
     *
90
     * @param int $id If set, this is the id of an existing record, used to load the data.
91
     * @param stdClass|null $record If set will be passed to from_record
92
     * @param null|array $metadata
93
     */
94
    public function __construct($id = 0, stdClass $record = null, ?array $metadata = null) {
95
        if ($record) {
96
            $record->headless = $record->headless ?? false;
97
            $record->imported = $record->imported ?? false;
98
            $record->groupid = $record->groupid ?? 0;
99
            $record->status = $record->status ?? self::RECORDING_STATUS_AWAITING;
100
        }
101
        parent::__construct($id, $record);
102
 
103
        if ($metadata) {
104
            $this->metadata = $metadata;
105
        }
106
    }
107
 
108
    /**
109
     * Helper function to retrieve recordings from the BigBlueButton.
110
     *
111
     * @param instance $instance
112
     * @param bool $includeimported
113
     * @param bool $onlyimported
114
     *
115
     * @return recording[] containing the recordings indexed by recordID, each recording is also a
116
     * non sequential associative array itself that corresponds to the actual recording in BBB
117
     */
118
    public static function get_recordings_for_instance(
119
        instance $instance,
120
        bool $includeimported = false,
121
        bool $onlyimported = false
122
    ): array {
123
        [$selects, $params] = self::get_basic_select_from_parameters(false, $includeimported, $onlyimported);
124
        $selects[] = "bigbluebuttonbnid = :bbbid";
125
        $params['bbbid'] = $instance->get_instance_id();
126
        $groupmode = groups_get_activity_groupmode($instance->get_cm());
127
        $context = $instance->get_context();
128
        if ($groupmode) {
129
            [$groupselects, $groupparams] = self::get_select_for_group(
130
                $groupmode,
131
                $context,
132
                $instance->get_course_id(),
133
                $instance->get_group_id(),
134
                $instance->get_cm()->groupingid
135
            );
136
            if ($groupselects) {
137
                $selects[] = $groupselects;
138
                $params = array_merge_recursive($params, $groupparams);
139
            }
140
        }
141
 
142
        $recordings = self::fetch_records($selects, $params);
143
        foreach ($recordings as $recording) {
144
            $recording->instance = $instance;
145
        }
146
 
147
        return $recordings;
148
    }
149
 
150
    /**
151
     * Helper function to retrieve recordings from a given course.
152
     *
153
     * @param int $courseid id for a course record or null
154
     * @param array $excludedinstanceid exclude recordings from instance ids
155
     * @param bool $includeimported
156
     * @param bool $onlyimported
157
     * @param bool $includedeleted
158
     * @param bool $onlydeleted
159
     *
160
     * @return recording[] containing the recordings indexed by recordID, each recording is also a
161
     * non sequential associative array itself that corresponds to the actual recording in BBB
162
     */
163
    public static function get_recordings_for_course(
164
        int $courseid,
165
        array $excludedinstanceid = [],
166
        bool $includeimported = false,
167
        bool $onlyimported = false,
168
        bool $includedeleted = false,
169
        bool $onlydeleted = false
170
    ): array {
171
        global $DB;
172
 
173
        [$selects, $params] = self::get_basic_select_from_parameters(
174
            $includedeleted,
175
            $includeimported,
176
            $onlyimported,
177
            $onlydeleted
178
        );
179
        if ($courseid) {
180
            $selects[] = "courseid = :courseid";
181
            $params['courseid'] = $courseid;
182
            $course = $DB->get_record('course', ['id' => $courseid]);
183
            $groupmode = groups_get_course_groupmode($course);
184
            $context = context_course::instance($courseid);
185
        } else {
186
            $context = \context_system::instance();
187
            $groupmode = NOGROUPS;
188
        }
189
 
190
        if ($groupmode) {
191
            [$groupselects, $groupparams] = self::get_select_for_group($groupmode, $context, $course->id);
192
            if ($groupselects) {
193
                $selects[] = $groupselects;
194
                $params = array_merge($params, $groupparams);
195
            }
196
        }
197
 
198
        if ($excludedinstanceid) {
199
            [$sqlexcluded, $paramexcluded] = $DB->get_in_or_equal($excludedinstanceid, SQL_PARAMS_NAMED, 'param', false);
200
            $selects[] = "bigbluebuttonbnid {$sqlexcluded}";
201
            $params = array_merge($params, $paramexcluded);
202
        }
203
 
204
        return self::fetch_records($selects, $params);
205
    }
206
 
207
    /**
208
     * Get select for given group mode and context
209
     *
210
     * @param int $groupmode
211
     * @param \context $context
212
     * @param int $courseid
213
     * @param int $groupid
214
     * @param int $groupingid
215
     * @return array
216
     */
217
    protected static function get_select_for_group($groupmode, $context, $courseid, $groupid = 0, $groupingid = 0): array {
218
        global $DB, $USER;
219
 
220
        $selects = [];
221
        $params = [];
222
        if ($groupmode) {
223
            $accessallgroups = has_capability('moodle/site:accessallgroups', $context) || $groupmode == VISIBLEGROUPS;
224
            if ($accessallgroups) {
225
                if ($context instanceof context_module) {
226
                    $allowedgroups = groups_get_all_groups($courseid, 0, $groupingid);
227
                } else {
228
                    $allowedgroups = groups_get_all_groups($courseid);
229
                }
230
            } else {
231
                if ($context instanceof context_module) {
232
                    $allowedgroups = groups_get_all_groups($courseid, $USER->id, $groupingid);
233
                } else {
234
                    $allowedgroups = groups_get_all_groups($courseid, $USER->id);
235
                }
236
            }
237
            $allowedgroupsid = array_map(function ($g) {
238
                return $g->id;
239
            }, $allowedgroups);
240
            if ($groupid || empty($allowedgroups)) {
241
                $selects[] = "groupid = :groupid";
242
                $params['groupid'] = ($groupid && in_array($groupid, $allowedgroupsid)) ?
243
                    $groupid : 0;
244
            } else {
245
                if ($accessallgroups) {
246
                    $allowedgroupsid[] = 0;
247
                }
248
                list($groupselects, $groupparams) = $DB->get_in_or_equal($allowedgroupsid, SQL_PARAMS_NAMED);
249
                $selects[] = 'groupid ' . $groupselects;
250
                $params = array_merge_recursive($params, $groupparams);
251
            }
252
        }
253
        return [
254
            implode(" AND ", $selects),
255
            $params,
256
        ];
257
    }
258
 
259
    /**
260
     * Get basic sql select from given parameters
261
     *
262
     * @param bool $includedeleted
263
     * @param bool $includeimported
264
     * @param bool $onlyimported
265
     * @param bool $onlydeleted
266
     * @return array
267
     */
268
    protected static function get_basic_select_from_parameters(
269
        bool $includedeleted = false,
270
        bool $includeimported = false,
271
        bool $onlyimported = false,
272
        bool $onlydeleted = false
273
    ): array {
274
        $selects = [];
275
        $params = [];
276
 
277
        // Start with the filters.
278
        if ($onlydeleted) {
279
            // Only headless recordings when only deleted is set.
280
            $selects[] = "headless = :headless";
281
            $params['headless'] = self::RECORDING_HEADLESS;
282
        } else if (!$includedeleted) {
283
            // Exclude headless recordings unless includedeleted.
284
            $selects[] = "headless != :headless";
285
            $params['headless'] = self::RECORDING_HEADLESS;
286
        }
287
 
288
        if (!$includeimported) {
289
            // Exclude imported recordings unless includedeleted.
290
            $selects[] = "imported != :imported";
291
            $params['imported'] = self::RECORDING_IMPORTED;
292
        } else if ($onlyimported) {
293
            // Exclude non-imported recordings.
294
            $selects[] = "imported = :imported";
295
            $params['imported'] = self::RECORDING_IMPORTED;
296
        }
297
 
298
        // Now get only recordings that have been validated by recording ready callback.
299
        $selects[] = "status IN (:status_processed, :status_notified)";
300
        $params['status_processed'] = self::RECORDING_STATUS_PROCESSED;
301
        $params['status_notified'] = self::RECORDING_STATUS_NOTIFIED;
302
        return [$selects, $params];
303
    }
304
 
305
    /**
306
     * Return the definition of the properties of this model.
307
     *
308
     * @return array
309
     */
310
    protected static function define_properties() {
311
        return [
312
            'courseid' => [
313
                'type' => PARAM_INT,
314
            ],
315
            'bigbluebuttonbnid' => [
316
                'type' => PARAM_INT,
317
            ],
318
            'groupid' => [
319
                'type' => PARAM_INT,
320
                'null' => NULL_ALLOWED,
321
            ],
322
            'recordingid' => [
323
                'type' => PARAM_RAW,
324
            ],
325
            'headless' => [
326
                'type' => PARAM_BOOL,
327
            ],
328
            'imported' => [
329
                'type' => PARAM_BOOL,
330
            ],
331
            'status' => [
332
                'type' => PARAM_INT,
333
            ],
334
            'importeddata' => [
335
                'type' => PARAM_RAW,
336
                'null' => NULL_ALLOWED,
337
                'default' => ''
338
            ],
339
            'name' => [
340
                'type' => PARAM_TEXT,
341
                'null' => NULL_ALLOWED,
342
                'default' => null
343
            ],
344
            'description' => [
345
                'type' => PARAM_TEXT,
346
                'null' => NULL_ALLOWED,
347
                'default' => 0
348
            ],
349
            'protected' => [
350
                'type' => PARAM_BOOL,
351
                'null' => NULL_ALLOWED,
352
                'default' => null
353
            ],
354
            'starttime' => [
355
                'type' => PARAM_INT,
356
                'null' => NULL_ALLOWED,
357
                'default' => null
358
            ],
359
            'endtime' => [
360
                'type' => PARAM_INT,
361
                'null' => NULL_ALLOWED,
362
                'default' => null
363
            ],
364
            'published' => [
365
                'type' => PARAM_BOOL,
366
                'null' => NULL_ALLOWED,
367
                'default' => null
368
            ],
369
            'playbacks' => [
370
                'type' => PARAM_RAW,
371
                'null' => NULL_ALLOWED,
372
                'default' => null
373
            ],
374
        ];
375
    }
376
 
377
    /**
378
     * Get the instance that this recording relates to.
379
     *
380
     * @return instance
381
     */
382
    public function get_instance(): instance {
383
        if ($this->instance === null) {
384
            $this->instance = instance::get_from_instanceid($this->get('bigbluebuttonbnid'));
385
        }
386
 
387
        return $this->instance;
388
    }
389
 
390
    /**
391
     * Before doing the database update, let's check if we need to update metadata
392
     *
393
     * @return void
394
     */
395
    protected function before_update() {
396
        // We update if the remote metadata has been changed locally.
397
        if ($this->metadatachanged && !$this->get('imported')) {
398
            $metadata = $this->fetch_metadata();
399
            if ($metadata) {
400
                recording_proxy::update_recording(
401
                    $this->get('recordingid'),
402
                    $metadata
403
                );
404
            }
405
            $this->metadatachanged = false;
406
        }
407
    }
408
 
409
    /**
410
     * Create a new imported recording from current recording
411
     *
412
     * @param instance $targetinstance
413
     * @return recording
414
     */
415
    public function create_imported_recording(instance $targetinstance) {
416
        $recordingrec = $this->to_record();
417
        $remotedata = $this->fetch_metadata();
418
        unset($recordingrec->id);
419
        $recordingrec->bigbluebuttonbnid = $targetinstance->get_instance_id();
420
        $recordingrec->courseid = $targetinstance->get_course_id();
421
        $recordingrec->groupid = 0; // The recording is available to everyone.
422
        $recordingrec->importeddata = json_encode($remotedata);
423
        $recordingrec->imported = true;
424
        $recordingrec->headless = false;
425
        $importedrecording = new self(0, $recordingrec);
426
        $importedrecording->create();
427
        return $importedrecording;
428
    }
429
 
430
    /**
431
     * Delete the recording in the BBB button
432
     *
433
     * @return void
434
     */
435
    protected function before_delete() {
436
        $recordid = $this->get('recordingid');
437
        if ($recordid && !$this->get('imported')) {
438
            recording_proxy::delete_recording($recordid);
439
            // Delete in cache if needed.
440
            $cachedrecordings = cache::make('mod_bigbluebuttonbn', 'recordings');
441
            $cachedrecordings->delete($recordid);
442
        }
443
    }
444
 
445
    /**
446
     * Set name
447
     *
448
     * @param string $value
449
     */
450
    protected function set_name($value) {
451
        $this->metadata_set('name', trim($value));
452
    }
453
 
454
    /**
455
     * Set Description
456
     *
457
     * @param string $value
458
     */
459
    protected function set_description($value) {
460
        $this->metadata_set('description', trim($value));
461
    }
462
 
463
    /**
464
     * Recording is protected
465
     *
466
     * @param bool $value
467
     */
468
    protected function set_protected($value) {
469
        $realvalue = $value ? "true" : "false";
470
        $this->metadata_set('protected', $realvalue);
471
        recording_proxy::protect_recording($this->get('recordingid'), $realvalue);
472
    }
473
 
474
    /**
475
     * Recording starttime
476
     *
477
     * @param int $value
478
     */
479
    protected function set_starttime($value) {
480
        $this->metadata_set('starttime', $value);
481
    }
482
 
483
    /**
484
     * Recording endtime
485
     *
486
     * @param int $value
487
     */
488
    protected function set_endtime($value) {
489
        $this->metadata_set('endtime', $value);
490
    }
491
 
492
    /**
493
     * Recording is published
494
     *
495
     * @param bool $value
496
     */
497
    protected function set_published($value) {
498
        $realvalue = $value ? "true" : "false";
499
        $this->metadata_set('published', $realvalue);
500
        // Now set this flag onto the remote bbb server.
501
        recording_proxy::publish_recording($this->get('recordingid'), $realvalue);
502
    }
503
 
504
    /**
505
     * Update recording status
506
     *
507
     * @param bool $value
508
     */
509
    protected function set_status($value) {
510
        $this->raw_set('status', $value);
511
        $this->update();
512
    }
513
 
514
    /**
515
     * POSSIBLE_REMOTE_META_SOURCE match a field type and its metadataname (historical and current).
516
     */
517
    const POSSIBLE_REMOTE_META_SOURCE = [
518
        'description' => ['meta_bbb-recording-description', 'meta_contextactivitydescription'],
519
        'name' => ['meta_bbb-recording-name', 'meta_contextactivity', 'meetingName'],
520
        'playbacks' => ['playbacks'],
521
        'starttime' => ['startTime'],
522
        'endtime' => ['endTime'],
523
        'published' => ['published'],
524
        'protected' => ['protected'],
525
        'tags' => ['meta_bbb-recording-tags']
526
    ];
527
 
528
    /**
529
     * Get the real metadata name for the possible source.
530
     *
531
     * @param string $sourcetype the name of the source we look for (name, description...)
532
     * @param array $metadata current metadata
533
     */
534
    protected function get_possible_meta_name_for_source($sourcetype, $metadata): string {
535
        $possiblesource = self::POSSIBLE_REMOTE_META_SOURCE[$sourcetype];
536
        $possiblesourcename = $possiblesource[0];
537
        foreach ($possiblesource as $possiblesname) {
538
            if (isset($meta[$possiblesname])) {
539
                $possiblesourcename = $possiblesname;
540
            }
541
        }
542
        return $possiblesourcename;
543
    }
544
 
545
    /**
546
     * Convert string (metadata) to json object
547
     *
548
     * @return mixed|null
549
     */
550
    protected function remote_meta_convert() {
551
        $remotemeta = $this->raw_get('importeddata');
552
        return json_decode($remotemeta, true);
553
    }
554
 
555
    /**
556
     * Description is stored in the metadata, so we sometimes needs to do some conversion.
557
     */
558
    protected function get_description() {
559
        return trim($this->metadata_get('description'));
560
    }
561
 
562
    /**
563
     * Name is stored in the metadata
564
     */
565
    protected function get_name() {
566
        return trim($this->metadata_get('name'));
567
    }
568
 
569
    /**
570
     * List of playbacks for this recording.
571
     *
572
     * @return array[]
573
     */
574
    protected function get_playbacks() {
575
        if ($playbacks = $this->metadata_get('playbacks')) {
576
            return array_map(function (array $playback): array {
577
                $clone = array_merge([], $playback);
578
                $clone['url'] = new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [
579
                    'action' => 'play',
580
                    'bn' => $this->raw_get('bigbluebuttonbnid'),
581
                    'rid' => $this->get('id'),
582
                    'rtype' => $clone['type'],
583
                ]);
584
 
585
                return $clone;
586
            }, $playbacks);
587
        }
588
 
589
        return [];
590
    }
591
 
592
    /**
593
     * Get the playback URL for the specified type.
594
     *
595
     * @param string $type
596
     * @return null|string
597
     */
598
    public function get_remote_playback_url(string $type): ?string {
599
        $this->refresh_metadata_if_required();
600
 
601
        $playbacks = $this->metadata_get('playbacks');
602
        foreach ($playbacks as $playback) {
603
            if ($playback['type'] == $type) {
604
                return $playback['url'];
605
            }
606
        }
607
 
608
        return null;
609
    }
610
 
611
    /**
612
     * Is protected. Return null if protected is not implemented.
613
     *
614
     * @return bool|null
615
     */
616
    protected function get_protected() {
617
        $protectedtext = $this->metadata_get('protected');
618
        return is_null($protectedtext) ? null : $protectedtext === "true";
619
    }
620
 
621
    /**
622
     * Start time
623
     *
624
     * @return mixed|null
625
     */
626
    protected function get_starttime() {
627
        return $this->metadata_get('starttime');
628
    }
629
 
630
    /**
631
     * Start time
632
     *
633
     * @return mixed|null
634
     */
635
    protected function get_endtime() {
636
        return $this->metadata_get('endtime');
637
    }
638
 
639
    /**
640
     * Is published
641
     *
642
     * @return bool
643
     */
644
    protected function get_published() {
645
        $publishedtext = $this->metadata_get('published');
646
        return $publishedtext === "true";
647
    }
648
 
649
    /**
650
     * Set locally stored metadata from this instance
651
     *
652
     * @param string $fieldname
653
     * @param mixed $value
654
     */
655
    protected function metadata_set($fieldname, $value) {
656
        // Can we can change the metadata on the imported record ?
657
        if ($this->get('imported')) {
658
            return;
659
        }
660
 
661
        $this->metadatachanged = true;
662
 
663
        $metadata = $this->fetch_metadata();
664
        $possiblesourcename = $this->get_possible_meta_name_for_source($fieldname, $metadata);
665
        $metadata[$possiblesourcename] = $value;
666
 
667
        $this->metadata = $metadata;
668
    }
669
 
670
    /**
671
     * Get information stored in the recording metadata such as description, name and other info
672
     *
673
     * @param string $fieldname
674
     * @return mixed|null
675
     */
676
    protected function metadata_get($fieldname) {
677
        $metadata = $this->fetch_metadata();
678
 
679
        $possiblesourcename = $this->get_possible_meta_name_for_source($fieldname, $metadata);
680
        return $metadata[$possiblesourcename] ?? null;
681
    }
682
 
683
    /**
684
     * @var string Default sort for recordings when fetching from the database.
685
     */
686
    const DEFAULT_RECORDING_SORT = 'timecreated ASC';
687
 
688
    /**
689
     * Fetch all records which match the specified parameters, including all metadata that relates to them.
690
     *
691
     * @param array $selects
692
     * @param array $params
693
     * @return recording[]
694
     */
695
    protected static function fetch_records(array $selects, array $params): array {
696
        global $DB, $CFG;
697
 
698
        $withindays = time() - (self::RECORDING_TIME_LIMIT_DAYS * DAYSECS);
699
        // Sort for recordings when fetching from the database.
700
        $recordingsort = $CFG->bigbluebuttonbn_recordings_asc_sort ? 'timecreated ASC' : 'timecreated DESC';
701
 
702
        // Fetch the local data. Arbitrary sort by id, so we get the same result on different db engines.
703
        $recordings = $DB->get_records_select(
704
            static::TABLE,
705
            implode(" AND ", $selects),
706
            $params,
707
            $recordingsort
708
        );
709
 
710
        // Grab the recording IDs.
711
        $recordingids = array_values(array_map(function ($recording) {
712
            return $recording->recordingid;
713
        }, $recordings));
714
 
715
        // Fetch all metadata for these recordings.
716
        $metadatas = recording_proxy::fetch_recordings($recordingids);
717
 
718
        // Return the instances.
719
        return array_filter(array_map(function ($recording) use ($metadatas, $withindays) {
720
            // Filter out if no metadata was fetched.
721
            if (!array_key_exists($recording->recordingid, $metadatas)) {
722
                // Mark it as dismissed if it is older than 30 days.
723
                if ($withindays > $recording->timecreated) {
724
                    $recording = new self(0, $recording, null);
725
                    $recording->set_status(self::RECORDING_STATUS_DISMISSED);
726
                }
727
                return false;
728
            }
729
            $metadata = $metadatas[$recording->recordingid];
730
            // Filter out and mark it as deleted if it was deleted in BBB.
731
            if ($metadata['state'] == 'deleted') {
732
                $recording = new self(0, $recording, null);
733
                $recording->set_status(self::RECORDING_STATUS_DELETED);
734
                return false;
735
            }
736
            // Include it otherwise.
737
            return new self(0, $recording, $metadata);
738
        }, $recordings));
739
    }
740
 
741
    /**
742
     * Fetch metadata
743
     *
744
     * If metadata has changed locally or if it an imported recording, nothing will be done.
745
     *
746
     * @param bool $force
747
     * @return array
748
     */
749
    protected function fetch_metadata(bool $force = false): ?array {
750
        if ($this->metadata !== null && !$force) {
751
            // Metadata is already up-to-date.
752
            return $this->metadata;
753
        }
754
 
755
        if ($this->get('imported')) {
756
            $this->metadata = json_decode($this->get('importeddata'), true);
757
        } else {
758
            $this->metadata = recording_proxy::fetch_recording($this->get('recordingid'));
759
        }
760
 
761
        return $this->metadata;
762
    }
763
 
764
    /**
765
     * Refresh metadata if required.
766
     *
767
     * If this is a protected recording which whose data was not fetched in the current request, then the metadata will
768
     * be purged and refetched. This ensures that the url is safe for use with a protected recording.
769
     */
770
    protected function refresh_metadata_if_required() {
771
        recording_proxy::purge_protected_recording($this->get('recordingid'));
772
        $this->fetch_metadata(true);
773
    }
774
 
775
    /**
776
     * Synchronise pending recordings from the server.
777
     *
778
     * This function should be called by the check_pending_recordings scheduled task.
779
     *
780
     * @param bool $dismissedonly fetch dismissed recording only
781
     */
782
    public static function sync_pending_recordings_from_server(bool $dismissedonly = false): void {
783
        global $DB;
784
        $params = [
785
            'withindays' => time() - (self::RECORDING_TIME_LIMIT_DAYS * DAYSECS),
786
        ];
787
        // Fetch the local data.
788
        if ($dismissedonly) {
789
            mtrace("=> Looking for any recording that has been 'dismissed' in the past " . self::RECORDING_TIME_LIMIT_DAYS
790
                . " days.");
791
            $select = 'status = :status_dismissed AND timecreated > :withindays';
792
            $params['status_dismissed'] = self::RECORDING_STATUS_DISMISSED;
793
        } else {
794
            mtrace("=> Looking for any recording awaiting processing from the past " . self::RECORDING_TIME_LIMIT_DAYS . " days.");
795
            $select = '(status = :status_awaiting AND timecreated > :withindays) OR status = :status_reset';
796
            $params['status_reset'] = self::RECORDING_STATUS_RESET;
797
            $params['status_awaiting'] = self::RECORDING_STATUS_AWAITING;
798
        }
799
 
800
        $recordings = $DB->get_records_select(static::TABLE, $select, $params, self::DEFAULT_RECORDING_SORT);
801
        // Sort by DEFAULT_RECORDING_SORT we get the same result on different db engines.
802
 
803
        $recordingcount = count($recordings);
804
        mtrace("=> Found {$recordingcount} recordings to query");
805
 
806
        // Grab the recording IDs.
807
        $recordingids = array_map(function($recording) {
808
            return $recording->recordingid;
809
        }, $recordings);
810
 
811
        // Fetch all metadata for these recordings.
812
        mtrace("=> Fetching recording metadata from server");
813
        $metadatas = recording_proxy::fetch_recordings($recordingids);
814
 
815
        $foundcount = 0;
816
        foreach ($metadatas as $recordingid => $metadata) {
817
            mtrace("==> Found metadata for {$recordingid}.");
818
            $id = array_search($recordingid, $recordingids);
819
            if (!$id) {
820
                // Recording was not found, skip.
821
                mtrace("===> Skip as fetched recording was not found.");
822
                continue;
823
            }
824
            // Recording was found, update status.
825
            mtrace("===> Update local cache as fetched recording was found.");
826
            $recording = new self(0, $recordings[$id], $metadata);
827
            $recording->set_status(self::RECORDING_STATUS_PROCESSED);
828
            $foundcount++;
829
 
830
            if (array_key_exists('breakouts', $metadata)) {
831
                // Iterate breakout recordings (if any) and update status.
832
                foreach ($metadata['breakouts'] as $breakoutrecordingid => $breakoutmetadata) {
833
                    $breakoutrecording = self::get_record(['recordingid' => $breakoutrecordingid]);
834
                    if (!$breakoutrecording) {
835
                        $breakoutrecording = new recording(0, (object) [
836
                            'courseid' => $recording->get('courseid'),
837
                            'bigbluebuttonbnid' => $recording->get('bigbluebuttonbnid'),
838
                            'groupid' => $recording->get('groupid'),
839
                            'recordingid' => $breakoutrecordingid
840
                        ], $breakoutmetadata);
841
                        $breakoutrecording->create();
842
                    }
843
                    $breakoutrecording->set_status(self::RECORDING_STATUS_PROCESSED);
844
                    $foundcount++;
845
                }
846
            }
847
        }
848
 
849
        mtrace("=> Finished processing recordings. Updated status for {$foundcount} / {$recordingcount} recordings.");
850
    }
851
}