Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | 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
/**
18
 * mod_bigbluebuttonbn data generator
19
 *
20
 * @package    mod_bigbluebuttonbn
21
 * @category   test
22
 * @copyright  2018 - present, Blindside Networks Inc
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 * @author     Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
25
 */
26
 
27
use core\plugininfo\mod;
28
use mod_bigbluebuttonbn\instance;
29
use mod_bigbluebuttonbn\local\config;
30
use mod_bigbluebuttonbn\logger;
31
use mod_bigbluebuttonbn\recording;
32
 
33
/**
34
 * bigbluebuttonbn module data generator
35
 *
36
 * @package    mod_bigbluebuttonbn
37
 * @category   test
38
 * @copyright  2018 - present, Blindside Networks Inc
39
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40
 * @author     Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
41
 */
42
class mod_bigbluebuttonbn_generator extends \testing_module_generator {
43
 
44
    /**
45
     * Creates an instance of bigbluebuttonbn for testing purposes.
46
     *
47
     * @param array|stdClass $record data for module being generated.
48
     * @param null|array $options general options for course module.
49
     * @return stdClass record from module-defined table with additional field cmid
50
     */
1441 ariadna 51
    public function create_instance($record = null, ?array $options = null) {
1 efrain 52
        // Prior to creating the instance, make sure that the BigBlueButton module is enabled.
53
        $modules = \core_plugin_manager::instance()->get_plugins_of_type('mod');
54
        if (!$modules['bigbluebuttonbn']->is_enabled()) {
55
            mod::enable_plugin('bigbluebuttonbn', true);
56
        }
57
 
58
        $now = time();
59
        $defaults = [
60
            "type" => 0,
61
            "meetingid" => sha1(rand()),
62
            "record" => true,
63
            "moderatorpass" => "mp",
64
            "viewerpass" => "ap",
65
            "participants" => "{}",
66
            "timecreated" => $now,
67
            "timemodified" => $now,
68
            "presentation" => null,
1441 ariadna 69
            "recordings_preview" => 0,
70
            "grade" => 0,
1 efrain 71
        ];
72
 
73
        $record = (array) $record;
74
 
75
        $record['participants'] = json_encode($this->get_participants_from_record($record));
76
 
77
        foreach ($defaults as $key => $value) {
78
            if (!isset($record[$key])) {
79
                $record[$key] = $value;
80
            }
81
        }
82
        if ($record['presentation']) {
83
            global $USER;
84
            // Here we replace the original presentation file with a draft area in which we store this file.
85
            $draftareaid = file_get_unused_draft_itemid();
86
            $bbbfilerecord['contextid'] = context_user::instance($USER->id)->id;
87
            $bbbfilerecord['component'] = 'user';
88
            $bbbfilerecord['filearea'] = 'draft';
89
            $bbbfilerecord['itemid'] = $draftareaid;
90
            $bbbfilerecord['filepath'] = '/';
91
            $bbbfilerecord['filename'] = basename($record['presentation']);
92
            $fs = get_file_storage();
93
 
94
            $fs->create_file_from_pathname($bbbfilerecord, $record['presentation']);
95
            // Now the $record['presentation'] must contain the draftareaid.
96
            $record['presentation'] = $draftareaid;
97
        }
98
        return parent::create_instance((object) $record, (array) $options);
99
    }
100
 
101
    /**
102
     * Create the participants field data from create_instance data.
103
     *
104
     * @param array $record
105
     * @return array
106
     */
107
    protected function get_participants_from_record(array $record): array {
108
        $roles = [];
109
        if (array_key_exists('moderators', $record) && !empty($record['moderators'])) {
110
            $roles = array_merge(
111
                $roles,
112
                $this->get_participant_configuration($record['moderators'], 'moderator')
113
            );
114
            unset($record['moderators']);
115
        }
116
 
117
        if (array_key_exists('viewers', $record) && !empty($record['viewers'])) {
118
            $roles = array_merge(
119
                $roles,
120
                $this->get_participant_configuration($record['viewers'], 'viewer')
121
            );
122
            unset($record['viewers']);
123
        }
124
 
125
        if (!empty($roles)) {
126
            array_unshift($roles, (object) [
127
                'selectiontype' => 'all',
128
                'selectionid' => 'all',
129
                'role' => 'viewer',
130
            ]);
131
        }
132
 
133
        return $roles;
134
    }
135
 
136
    /**
137
     * Get the participant configuration for a field and role for use in get_participants_from_record.
138
     *
139
     * @param string $field
140
     * @param string $role
141
     * @return array
142
     */
143
    protected function get_participant_configuration(string $field, string $role): array {
144
        global $DB;
145
 
146
        $values = explode(',', $field);
147
 
148
        $roles = $DB->get_records_menu('role', [], '', 'shortname, id');
149
 
150
        $configuration = [];
151
        foreach ($values as $value) {
152
            if (empty($value)) {
153
                // Empty value.
154
                continue;
155
            }
156
            [$type, $name] = explode(':', $value);
157
 
158
            $participant = (object) [
159
                'selectiontype' => $type,
160
                'role' => $role,
161
            ];
162
            switch ($type) {
163
                case 'role':
164
                    if (!array_key_exists($name, $roles)) {
165
                        throw new \coding_exception("Unknown role '{$name}'");
166
                    }
167
                    $participant->selectionid = $roles[$name];
168
 
169
                    break;
170
                case 'user':
171
                    $participant->selectionid = $DB->get_field('user', 'id', ['username' => $name], MUST_EXIST);
172
                    break;
173
                default:
174
                    throw new \coding_exception("Unknown participant type: '{$type}'");
175
            }
176
 
177
            $configuration[] = $participant;
178
        }
179
 
180
        return $configuration;
181
    }
182
 
183
    /**
184
     * Create a recording for the given bbb activity.
185
     *
186
     * The recording is created both locally, and a recording record is created on the mocked BBB server.
187
     *
188
     * @param array $data
189
     * @param bool $serveronly create it only on the server, not in the database.
190
     * @return stdClass the recording object
191
     */
192
    public function create_recording(array $data, $serveronly = false): stdClass {
193
        $instance = instance::get_from_instanceid($data['bigbluebuttonbnid']);
194
 
195
        if (isset($data['imported']) && filter_var($data['imported'], FILTER_VALIDATE_BOOLEAN)) {
196
            if (empty($data['importedid'])) {
197
                throw new moodle_exception('error');
198
            }
199
            $recording = recording::get_record(['recordingid' => $data['importedid']]);
200
            $recording->imported = true;
201
        } else {
202
            $recording = (object) [
203
                'headless' => false,
204
                'imported' => false,
205
                'status' => $data['status'] ?? recording::RECORDING_STATUS_NOTIFIED,
206
            ];
207
        }
208
 
209
        if (!empty($data['groupid'])) {
210
            $instance->set_group_id($data['groupid']);
211
            $recording->groupid = $data['groupid'];
212
        }
213
 
214
        $recording->bigbluebuttonbnid = $instance->get_instance_id();
215
        $recording->courseid = $instance->get_course_id();
216
        if (isset($options['imported']) && $options['imported']) {
217
            $precording = $recording->create_imported_recording($instance);
218
        } else {
219
            if ($recording->status == recording::RECORDING_STATUS_DISMISSED) {
220
                $recording->recordingid = sprintf(
221
                    "%s-%s",
222
                    md5($instance->get_meeting_id()),
223
                    time() + rand(1, 100000)
224
                );
225
            } else {
226
                $recording->recordingid = $this->create_mockserver_recording($instance, $recording, $data);
227
            }
228
            $precording = new recording(0, $recording);
229
            if (!$serveronly) {
230
                $precording->create();
231
            }
232
        }
233
        return $precording->to_record();
234
    }
235
 
236
    /**
237
     * Add a recording in the mock server
238
     *
239
     * @param instance $instance
240
     * @param stdClass $recordingdata
241
     * @param array $data
242
     * @return string
243
     */
244
    protected function create_mockserver_recording(instance $instance, stdClass $recordingdata, array $data): string {
245
        $now = time();
246
        $mockdata = array_merge((array) $recordingdata, [
247
            'sequence' => 1,
248
            'meta' => [
249
                'bn-presenter-name' => $data['presentername'] ?? 'Fake presenter',
11 efrain 250
                'bbb-recording-ready-url' => new moodle_url('/mod/bigbluebuttonbn/bbb_broker.php', [
1 efrain 251
                    'action' => 'recording_ready',
252
                    'bigbluebuttonbn' => $instance->get_instance_id()
253
                ]),
254
                'bbb-recording-description' => $data['description'] ?? '',
255
                'bbb-recording-name' => $data['name'] ?? '',
256
                'bbb-recording-tags' => $data['tags'] ?? '',
257
            ],
258
        ]);
259
        $mockdata['startTime'] = $data['starttime'] ?? $now;
260
        $mockdata['endTime'] = $data['endtime'] ?? $mockdata['startTime'] + HOURSECS;
261
        if (!empty($data['playback'])) {
262
            $mockdata['playback'] = json_encode($data['playback']);
263
        }
264
        if (!empty($data['isBreakout'])) {
265
            // If it is a breakout meeting, we do not have any way to know the real Id of the meeting
266
            // unless we query the list of submeetings.
267
            // For now we will just send the parent ID and let the mock server deal with the sequence + parentID
268
            // to find the meetingID.
269
            $mockdata['parentMeetingID'] = $instance->get_meeting_id();
270
        } else {
271
            $mockdata['meetingID'] = $instance->get_meeting_id();
272
        }
273
 
274
        $result = $this->send_mock_request('backoffice/createRecording', [], $mockdata);
275
 
276
        return (string) $result->recordID;
277
    }
278
 
279
    /**
280
     * Utility to send a request to the mock server
281
     *
282
     * @param string $endpoint
283
     * @param array $params
284
     * @param array $mockdata
285
     * @return SimpleXMLElement|bool
286
     * @throws moodle_exception
287
     */
288
    protected function send_mock_request(string $endpoint, array $params = [], array $mockdata = []): SimpleXMLElement {
289
        $url = $this->get_mocked_server_url($endpoint, $params);
290
 
291
        foreach ($mockdata as $key => $value) {
292
            if (is_array($value)) {
293
                foreach ($value as $subkey => $subvalue) {
294
                    $paramname = "{$key}_{$subkey}";
295
                    $url->param($paramname, $subvalue);
296
                }
297
            } else {
298
                $url->param($key, $value);
299
            }
300
        }
301
 
302
        $curl = new \curl();
303
        $result = $curl->get($url->out_omit_querystring(), $url->params());
304
 
305
        $retvalue = @simplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOBLANKS);
306
        if ($retvalue === false) {
307
            throw new moodle_exception('mockserverconnfailed', 'mod_bigbluebutton');
308
        }
309
        return $retvalue;
310
    }
311
 
312
    /**
313
     * Get a URL for a mocked BBB server endpoint.
314
     *
315
     * @param string $endpoint
316
     * @param array $params
317
     * @return moodle_url
318
     */
319
    protected function get_mocked_server_url(string $endpoint = '', array $params = []): moodle_url {
320
        return new moodle_url(TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER . '/' . $endpoint, $params);
321
    }
322
 
323
    /**
324
     * Mock an in-progress meeting on the remote server.
325
     *
326
     * @param array $data
327
     * @return stdClass
328
     */
329
    public function create_meeting(array $data): stdClass {
330
        $instance = instance::get_from_instanceid($data['instanceid']);
331
 
332
        if (array_key_exists('groupid', $data)) {
333
            $instance = instance::get_group_instance_from_instance($instance, $data['groupid']);
334
        }
335
 
336
        $meetingid = $instance->get_meeting_id();
337
 
338
        // Default room configuration.
339
        $roomconfig = array_merge($data, [
340
            'meetingName' => $instance->get_meeting_name(),
341
            'attendeePW' => $instance->get_viewer_password(),
342
            'moderatorPW' => $instance->get_moderator_password(),
343
            'voiceBridge' => $instance->get_voice_bridge(),
344
            'meta' => [
345
                'bbb-context' => $instance->get_course()->fullname,
346
                'bbb-context-id' => $instance->get_course()->id,
347
                'bbb-context-label' => $instance->get_course()->shortname,
348
                'bbb-context-name' => $instance->get_course()->fullname,
349
                'bbb-origin' => 'Moodle',
350
                'bbb-origin-tag' => 'moodle-mod_bigbluebuttonbn (TODO version)',
351
                'bbb-recording-description' => $instance->get_meeting_description(),
352
                'bbb-recording-name' => $instance->get_meeting_name(),
353
            ],
354
        ]);
355
        if ((boolean) config::get('recordingready_enabled')) {
11 efrain 356
            $roomconfig['meta']['bbb-recording-ready-url'] = $instance->get_record_ready_url()->out(false);
1 efrain 357
        }
358
        if ((boolean) config::get('meetingevents_enabled')) {
359
            $roomconfig['meta']['analytics-callback-url'] = $instance->get_meeting_event_notification_url()->out(false);
360
        }
361
        if (!empty($roomconfig['isBreakout'])) {
362
            // If it is a breakout meeting, we do not have any way to know the real Id of the meeting
363
            // For now we will just send the parent ID and let the mock server deal with the sequence + parentID
364
            // to find the meetingID.
365
            $roomconfig['parentMeetingID'] = $instance->get_meeting_id();
366
        } else {
367
            $roomconfig['meetingID'] = $meetingid;
368
        }
369
        $this->send_mock_request('backoffice/createMeeting', [], $roomconfig);
370
        return (object) $roomconfig;
371
    }
372
 
373
    /**
374
     * Create a log record
375
     *
376
     * @param mixed $record
377
     * @param array|null $options
378
     */
1441 ariadna 379
    public function create_log($record, ?array $options = null) {
1 efrain 380
        $instance = instance::get_from_instanceid($record['bigbluebuttonbnid']);
381
 
382
        $record = array_merge([
383
            'meetingid' => $instance->get_meeting_id(),
384
        ], (array) $record);
385
 
386
        $testlogclass = new class extends logger {
387
            /**
388
             * Log test event
389
             *
390
             * @param instance $instance
391
             * @param array $record
392
             */
393
            public static function log_test_event(instance $instance, array $record): void {
394
                self::log(
395
                    $instance,
396
                    logger::EVENT_CREATE,
397
                    $record
398
                );
399
            }
400
        };
401
 
402
        $testlogclass::log_test_event($instance, $record);
403
    }
404
 
405
    /**
406
     * Set a value in the Mock server
407
     *
408
     * @param string $name
409
     * @param mixed $value
410
     * @return void
411
     * @throws moodle_exception
412
     */
413
    public function set_value(string $name, $value): void {
414
        if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) {
415
            $this->send_mock_request('backoffice/set', [], ['name' => $name, 'value' => json_encode($value)]);
416
        }
417
    }
418
 
419
    /**
420
     * Trigger a meeting event on BBB side
421
     *
422
     * @param object $user
423
     * @param instance $instance
424
     * @param string $eventtype
425
     * @param string|null $eventdata
426
     * @return void
427
     */
428
    public function add_meeting_event(object $user, instance $instance, string $eventtype, string $eventdata = ''): void {
429
        $this->send_mock_request('backoffice/addMeetingEvent', [
430
                'secret' => \mod_bigbluebuttonbn\local\config::DEFAULT_SHARED_SECRET,
431
                'meetingID' => $instance->get_meeting_id(),
432
                'attendeeID' => $user->id,
433
                'attendeeName' => fullname($user),
434
                'eventType' => $eventtype,
435
                'eventData' => $eventdata
436
            ]
437
        );
438
    }
439
 
440
    /**
441
     * Send all previously store events
442
     *
443
     * @param instance $instance
444
     * @return object|null
445
     */
446
    public function send_all_events(instance $instance): ?object {
447
        if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) {
448
            return $this->send_mock_request('backoffice/sendAllEvents', [
449
                'meetingID' => $instance->get_meeting_id(),
450
                'sendQuery' => false, // We get the result directly here.
451
                'secret' => \mod_bigbluebuttonbn\local\config::DEFAULT_SHARED_SECRET,
452
            ]);
453
        }
454
        return null;
455
    }
456
 
457
    /**
458
     * Reset the mock server
459
     */
460
    public function reset_mock(): void {
461
        if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) {
462
            $this->send_mock_request('backoffice/reset');
463
        }
464
    }
465
}