Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

plugin/bbb/lib/bbb.lib.php (1 issue)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * Class bbb
7
 * This script initiates a video conference session, calling the BigBlueButton
8
 * API BigBlueButton-Chamilo connector class
9
 */
10
class bbb
11
{
12
    public $url;
13
    public $salt;
14
    public $api;
15
    public $userCompleteName = '';
16
    public $protocol = 'http://';
17
    public $debug = false;
18
    public $logoutUrl = '';
19
    public $pluginEnabled = false;
20
    public $enableGlobalConference = false;
21
    public $enableGlobalConferencePerUser = false;
22
    public $isGlobalConference = false;
23
    public $groupSupport = false;
24
    public $userSupport = false;
25
    public $accessUrl = 1;
26
    public $userId = 0;
27
    public $plugin;
28
    private $courseCode;
29
    private $courseId;
30
    private $sessionId;
31
    private $groupId;
32
    private $maxUsersLimit;
33
34
    /**
35
     * Constructor (generates a connection to the API and the Chamilo settings
36
     * required for the connection to the video conference server)
37
     *
38
     * @param string $host
39
     * @param string $salt
40
     * @param bool   $isGlobalConference
41
     * @param int    $isGlobalPerUser
42
     */
43
    public function __construct(
44
        $host = '',
45
        $salt = '',
46
        $isGlobalConference = false,
47
        $isGlobalPerUser = 0
48
    ) {
49
        $this->courseCode = api_get_course_id();
50
        $this->courseId = api_get_course_int_id();
51
        $this->sessionId = api_get_session_id();
52
        $this->groupId = api_get_group_id();
53
54
        // Initialize video server settings from global settings
55
        $this->plugin = BBBPlugin::create();
56
        $bbbPluginEnabled = $this->plugin->get('tool_enable');
57
58
        $bbb_host = !empty($host) ? $host : $this->plugin->get('host');
59
        $bbb_salt = !empty($salt) ? $salt : $this->plugin->get('salt');
60
61
        $this->table = Database::get_main_table('plugin_bbb_meeting');
62
        $this->enableGlobalConference = $this->plugin->get('enable_global_conference') === 'true';
63
        $this->isGlobalConference = (bool) $isGlobalConference;
64
65
        $columns = Database::listTableColumns($this->table);
66
        $this->groupSupport = isset($columns['group_id']) ? true : false;
67
        $this->userSupport = isset($columns['user_id']) ? true : false;
68
        $this->accessUrl = api_get_current_access_url_id();
69
70
        $this->enableGlobalConferencePerUser = false;
71
        if ($this->userSupport && !empty($isGlobalPerUser)) {
72
            $this->enableGlobalConferencePerUser = $this->plugin->get('enable_global_conference_per_user') === 'true';
73
            $this->userId = $isGlobalPerUser;
74
        }
75
76
        if ($this->groupSupport) {
77
            // Plugin check
78
            $this->groupSupport = $this->plugin->get('enable_conference_in_course_groups') === 'true' ? true : false;
79
            if ($this->groupSupport) {
80
                // Platform check
81
                $bbbSetting = api_get_setting('bbb_enable_conference_in_course_groups');
82
                $bbbSetting = isset($bbbSetting['bbb']) ? $bbbSetting['bbb'] === 'true' : false;
83
84
                if ($bbbSetting) {
85
                    // Course check
86
                    $courseInfo = api_get_course_info();
87
                    if ($courseInfo) {
88
                        $this->groupSupport = api_get_course_plugin_setting(
89
                                'bbb',
90
                                'bbb_enable_conference_in_groups',
91
                                $courseInfo
92
                            ) === '1';
93
                    }
94
                }
95
            }
96
        }
97
        $this->maxUsersLimit = $this->plugin->get('max_users_limit');
98
99
        if ($bbbPluginEnabled === 'true') {
100
            $userInfo = api_get_user_info();
101
            if (empty($userInfo) && !empty($isGlobalPerUser)) {
102
                // If we are following a link to a global "per user" conference
103
                // then generate a random guest name to join the conference
104
                // because there is no part of the process where we give a name
105
                //$this->userCompleteName = 'Guest'.rand(1000, 9999);
106
            } else {
107
                $this->userCompleteName = $userInfo['complete_name'];
108
            }
109
110
            if (api_is_anonymous()) {
111
                $this->userCompleteName = get_lang('Guest').'_'.rand(1000, 9999);
112
            }
113
114
            $this->salt = $bbb_salt;
115
            if (!empty($bbb_host)) {
116
                if (substr($bbb_host, -1, 1) !== '/') {
117
                    $bbb_host .= '/';
118
                }
119
                $this->url = $bbb_host;
120
                if (!preg_match('#/bigbluebutton/$#', $bbb_host)) {
121
                    $this->url = $bbb_host.'bigbluebutton/';
122
                }
123
            }
124
            $info = parse_url($bbb_host);
125
126
            if (isset($info['scheme'])) {
127
                $this->protocol = $info['scheme'].'://';
128
                $this->url = str_replace($this->protocol, '', $this->url);
129
                $urlWithProtocol = $bbb_host;
130
            } else {
131
                // We assume it's an http, if user wants to use https, the host *must* include the protocol.
132
                $this->protocol = 'http://';
133
                $urlWithProtocol = $this->protocol.$bbb_host;
134
            }
135
136
            // Setting BBB api
137
            define('CONFIG_SECURITY_SALT', $this->salt);
138
            define('CONFIG_SERVER_URL_WITH_PROTOCOL', $urlWithProtocol);
139
            define('CONFIG_SERVER_BASE_URL', $this->url);
140
            define('CONFIG_SERVER_PROTOCOL', $this->protocol);
141
142
            $this->api = new BigBlueButtonBN();
143
            $this->pluginEnabled = true;
144
            $this->logoutUrl = $this->getListingUrl();
145
        }
146
    }
147
148
    /**
149
     * @param int $courseId  Optional. Course ID.
150
     * @param int $sessionId Optional. Session ID.
151
     * @param int $groupId   Optional. Group ID.
152
     *
153
     * @return string
154
     */
155
    public function getListingUrl($courseId = 0, $sessionId = 0, $groupId = 0)
156
    {
157
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'
158
            .$this->getUrlParams($courseId, $sessionId, $groupId);
159
    }
160
161
    /**
162
     * @param int $courseId  Optional. Course ID.
163
     * @param int $sessionId Optional. Session ID.
164
     * @param int $groupId   Optional. Group ID.
165
     *
166
     * @return string
167
     */
168
    public function getUrlParams($courseId = 0, $sessionId = 0, $groupId = 0)
169
    {
170
        if (empty($this->courseCode) && !$courseId) {
171
            if ($this->isGlobalConferencePerUserEnabled()) {
172
                return 'global=1&user_id='.$this->userId;
173
            }
174
175
            if ($this->isGlobalConference()) {
176
                return 'global=1';
177
            }
178
179
            return '';
180
        }
181
182
        $courseCode = $this->courseCode;
183
        if (!empty($courseId)) {
184
            $course = api_get_course_info_by_id($courseId);
185
            if ($course) {
186
                $courseCode = $course['code'];
187
            }
188
        }
189
190
        return http_build_query(
191
            [
192
                'cidReq' => $courseCode,
193
                'id_session' => $sessionId ?: $this->sessionId,
194
                'gidReq' => $groupId ?: $this->groupId,
195
            ]
196
        );
197
    }
198
199
    /**
200
     * @return bool
201
     */
202
    public function isGlobalConferencePerUserEnabled()
203
    {
204
        return $this->enableGlobalConferencePerUser;
205
    }
206
207
    /**
208
     * @return bool
209
     */
210
    public function isGlobalConference()
211
    {
212
        if ($this->isGlobalConferenceEnabled() === false) {
213
            return false;
214
        }
215
216
        return (bool) $this->isGlobalConference;
217
    }
218
219
    /**
220
     * @return bool
221
     */
222
    public function isGlobalConferenceEnabled()
223
    {
224
        return $this->enableGlobalConference;
225
    }
226
227
    /**
228
     * @param array $userInfo
229
     *
230
     * @return bool
231
     */
232
    public static function showGlobalConferenceLink($userInfo)
233
    {
234
        if (empty($userInfo)) {
235
            return false;
236
        }
237
        $setting = api_get_plugin_setting('bbb', 'enable_global_conference');
238
        $settingLink = api_get_plugin_setting('bbb', 'enable_global_conference_link');
239
        if ($setting === 'true' && $settingLink === 'true') {
240
            //$content = Display::url(get_lang('LaunchVideoConferenceRoom'), $url);
241
            $allowedRoles = api_get_plugin_setting(
242
                'bbb',
243
                'global_conference_allow_roles'
244
            );
245
246
            if (api_is_platform_admin()) {
247
                $userInfo['status'] = PLATFORM_ADMIN;
248
            }
249
250
            $showGlobalLink = true;
251
            if (!empty($allowedRoles)) {
252
                if (!in_array($userInfo['status'], $allowedRoles)) {
253
                    $showGlobalLink = false;
254
                }
255
            }
256
257
            return $showGlobalLink;
258
        }
259
    }
260
261
    /**
262
     * Gets the global limit of users in a video-conference room.
263
     * This value can be overridden by course-specific values
264
     * @return  int Maximum number of users set globally
265
     */
266
    public function getMaxUsersLimit()
267
    {
268
        $limit = $this->maxUsersLimit;
269
        if ($limit <= 0) {
270
            $limit = 0;
271
        }
272
        $courseLimit = 0;
273
        $sessionLimit = 0;
274
        // Check the extra fields for this course and session
275
        // Session limit takes priority over course limit
276
        // Course limit takes priority over global limit
277
        if (!empty($this->courseId)) {
278
            $extraField = new ExtraField('course');
279
            $fieldId = $extraField->get_all(
280
                array('variable = ?' => 'plugin_bbb_course_users_limit')
281
            );
282
            $extraValue = new ExtraFieldValue('course');
283
            $value = $extraValue->get_values_by_handler_and_field_id($this->courseId, $fieldId[0]['id']);
284
            if (!empty($value['value'])) {
285
                $courseLimit = (int) $value['value'];
286
            }
287
        }
288
        if (!empty($this->sessionId)) {
289
            $extraField = new ExtraField('session');
290
            $fieldId = $extraField->get_all(
291
                array('variable = ?' => 'plugin_bbb_session_users_limit')
292
            );
293
            $extraValue = new ExtraFieldValue('session');
294
            $value = $extraValue->get_values_by_handler_and_field_id($this->sessionId, $fieldId[0]['id']);
295
            if (!empty($value['value'])) {
296
                $sessionLimit = (int) $value['value'];
297
            }
298
        }
299
300
        if (!empty($sessionLimit)) {
301
            return $sessionLimit;
302
        } elseif (!empty($courseLimit)) {
303
            return $courseLimit;
304
        }
305
306
        return (int) $limit;
307
    }
308
309
    /**
310
     * Sets the global limit of users in a video-conference room.
311
     *
312
     * @param int Maximum number of users (globally)
313
     */
314
    public function setMaxUsersLimit($max)
315
    {
316
        if ($max < 0) {
317
            $max = 0;
318
        }
319
        $this->maxUsersLimit = (int) $max;
320
    }
321
322
    /**
323
     * See this file in you BBB to set up default values
324
     *
325
     * @param array $params Array of parameters that will be completed if not containing all expected variables
326
     *
327
     * /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
328
     *
329
     * More record information:
330
     * http://code.google.com/p/bigbluebutton/wiki/RecordPlaybackSpecification
331
     *
332
     * Default maximum number of users a meeting can have.
333
     * Doesn't get enforced yet but is the default value when the create
334
     * API doesn't pass a value.
335
     * defaultMaxUsers=20
336
     *
337
     * Default duration of the meeting in minutes.
338
     * Current default is 0 (meeting doesn't end).
339
     * defaultMeetingDuration=0
340
     *
341
     * Remove the meeting from memory when the end API is called.
342
     * This allows 3rd-party apps to recycle the meeting right-away
343
     * instead of waiting for the meeting to expire (see below).
344
     * removeMeetingWhenEnded=false
345
     *
346
     * The number of minutes before the system removes the meeting from memory.
347
     * defaultMeetingExpireDuration=1
348
     *
349
     * The number of minutes the system waits when a meeting is created and when
350
     * a user joins. If after this period, a user hasn't joined, the meeting is
351
     * removed from memory.
352
     * defaultMeetingCreateJoinDuration=5
353
     *
354
     * @return mixed
355
     */
356
    public function createMeeting($params)
357
    {
358
        $params['c_id'] = api_get_course_int_id();
359
        $params['session_id'] = api_get_session_id();
360
361
        if ($this->hasGroupSupport()) {
362
            $params['group_id'] = api_get_group_id();
363
        }
364
365
        if ($this->isGlobalConferencePerUserEnabled() && !empty($this->userId)) {
366
            $params['user_id'] = (int) $this->userId;
367
        }
368
369
        $params['attendee_pw'] = isset($params['attendee_pw']) ? $params['attendee_pw'] : $this->getUserMeetingPassword();
370
        $attendeePassword = $params['attendee_pw'];
371
        $params['moderator_pw'] = isset($params['moderator_pw']) ? $params['moderator_pw'] : $this->getModMeetingPassword();
372
        $moderatorPassword = $params['moderator_pw'];
373
374
        $params['record'] = api_get_course_plugin_setting('bbb', 'big_blue_button_record_and_store') == 1;
375
        $max = api_get_course_plugin_setting('bbb', 'big_blue_button_max_students_allowed');
376
        $max = isset($max) ? $max : -1;
377
378
        $params['status'] = 1;
379
        // Generate a pseudo-global-unique-id to avoid clash of conferences on
380
        // the same BBB server with several Chamilo portals
381
        $params['remote_id'] = uniqid(true, true);
382
        // Each simultaneous conference room needs to have a different
383
        // voice_bridge composed of a 5 digits number, so generating a random one
384
        $params['voice_bridge'] = rand(10000, 99999);
385
        $params['created_at'] = api_get_utc_datetime();
386
        $params['access_url'] = $this->accessUrl;
387
388
        $id = Database::insert($this->table, $params);
389
390
        if ($id) {
391
            Event::addEvent(
392
                'bbb_create_meeting',
393
                'meeting_id',
394
                (int) $id,
395
                null,
396
                api_get_user_id(),
397
                api_get_course_int_id(),
398
                api_get_session_id()
399
            );
400
401
            $meetingName = isset($params['meeting_name']) ? $params['meeting_name'] : $this->getCurrentVideoConferenceName();
402
            $welcomeMessage = isset($params['welcome_msg']) ? $params['welcome_msg'] : null;
403
            $record = isset($params['record']) && $params['record'] ? 'true' : 'false';
404
            //$duration = isset($params['duration']) ? intval($params['duration']) : 0;
405
            // This setting currently limits the maximum conference duration,
406
            // to avoid lingering sessions on the video-conference server #6261
407
            $duration = 300;
408
            $meetingDuration = (int) $this->plugin->get('meeting_duration');
409
            if (!empty($meetingDuration)) {
410
                $duration = $meetingDuration;
411
            }
412
            $bbbParams = array(
413
                'meetingId' => $params['remote_id'], // REQUIRED
414
                'meetingName' => $meetingName, // REQUIRED
415
                'attendeePw' => $attendeePassword, // Match this value in getJoinMeetingURL() to join as attendee.
416
                'moderatorPw' => $moderatorPassword, // Match this value in getJoinMeetingURL() to join as moderator.
417
                'welcomeMsg' => $welcomeMessage, // ''= use default. Change to customize.
418
                'dialNumber' => '', // The main number to call into. Optional.
419
                'voiceBridge' => $params['voice_bridge'], // PIN to join voice. Required.
420
                'webVoice' => '', // Alphanumeric to join voice. Optional.
421
                'logoutUrl' => $this->logoutUrl.'&action=logout&remote_id='.$params['remote_id'],
422
                'maxParticipants' => $max, // Optional. -1 = unlimitted. Not supported in BBB. [number]
423
                'record' => $record, // New. 'true' will tell BBB to record the meeting.
424
                'duration' => $duration, // Default = 0 which means no set duration in minutes. [number]
425
                //'meta_category' => '',  // Use to pass additional info to BBB server. See API docs.
426
            );
427
428
            $status = false;
429
            $meeting = null;
430
            while ($status === false) {
431
                $result = $this->api->createMeetingWithXmlResponseArray($bbbParams);
432
                if (isset($result) && strval($result['returncode']) == 'SUCCESS') {
433
                    if ($this->plugin->get('allow_regenerate_recording') === 'true') {
434
                        $internalId = Database::escape_string($result['internalMeetingID']);
435
                        $sql = "UPDATE $this->table SET internal_meeting_id = '".$internalId."'
436
                                WHERE id = $id";
437
                        Database::query($sql);
438
                    }
439
                    $meeting = $this->joinMeeting($meetingName, true);
440
441
                    return $meeting;
442
                }
443
            }
444
        }
445
446
        return false;
447
    }
448
449
    /**
450
     * @return bool
451
     */
452
    public function hasGroupSupport()
453
    {
454
        return $this->groupSupport;
455
    }
456
457
    /**
458
     * Gets the password for a specific meeting for the current user
459
     *
460
     * @param string $courseCode
461
     *
462
     * @return string A moderator password if user is teacher, or the course code otherwise
463
     *
464
     */
465
    public function getUserMeetingPassword($courseCode = null)
466
    {
467
        if ($this->isGlobalConferencePerUserEnabled()) {
468
            return 'url_'.$this->userId.'_'.api_get_current_access_url_id();
469
        }
470
471
        if ($this->isGlobalConference()) {
472
            return 'url_'.api_get_current_access_url_id();
473
        }
474
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
475
476
        return $courseCode;
477
    }
478
479
    /**
480
     * Generated a moderator password for the meeting.
481
     *
482
     * @param string $courseCode
483
     *
484
     * @return string A password for the moderation of the videoconference
485
     */
486
    public function getModMeetingPassword($courseCode = null)
487
    {
488
        if ($this->isGlobalConferencePerUserEnabled()) {
489
            return 'url_'.$this->userId.'_'.api_get_current_access_url_id().'_mod';
490
        }
491
492
        if ($this->isGlobalConference()) {
493
            return 'url_'.api_get_current_access_url_id().'_mod';
494
        }
495
496
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
497
498
        return $courseCode.'mod';
499
    }
500
501
    /**
502
     * @return string
503
     */
504
    public function getCurrentVideoConferenceName()
505
    {
506
        if ($this->isGlobalConferencePerUserEnabled()) {
507
            return 'url_'.$this->userId.'_'.api_get_current_access_url_id();
508
        }
509
510
        if ($this->isGlobalConference()) {
511
            return 'url_'.api_get_current_access_url_id();
512
        }
513
514
        if ($this->hasGroupSupport()) {
515
            return api_get_course_id().'-'.api_get_session_id().'-'.api_get_group_id();
516
        }
517
518
        return api_get_course_id().'-'.api_get_session_id();
519
    }
520
521
    /**
522
     * Returns a meeting "join" URL
523
     *
524
     * @param string The name of the meeting (usually the course code)
525
     *
526
     * @return mixed The URL to join the meeting, or false on error
527
     * @todo implement moderator pass
528
     * @assert ('') === false
529
     * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
530
     */
531
    public function joinMeeting($meetingName)
532
    {
533
        if ($this->debug) {
534
            error_log("joinMeeting: $meetingName");
535
        }
536
537
        if (empty($meetingName)) {
538
            return false;
539
        }
540
541
        $manager = $this->isConferenceManager();
542
        if ($manager) {
543
            $pass = $this->getModMeetingPassword();
544
        } else {
545
            $pass = $this->getUserMeetingPassword();
546
        }
547
548
        $meetingData = Database::select(
549
            '*',
550
            $this->table,
551
            array(
552
                'where' => array(
553
                    'meeting_name = ? AND status = 1 AND access_url = ?' => array(
554
                        $meetingName,
555
                        $this->accessUrl,
556
                    ),
557
                ),
558
            ),
559
            'first'
560
        );
561
562
        if (empty($meetingData) || !is_array($meetingData)) {
563
            if ($this->debug) {
564
                error_log("meeting does not exist: $meetingName");
565
            }
566
567
            return false;
568
        }
569
570
        $params = array(
571
            'meetingId' => $meetingData['remote_id'],
572
            //  -- REQUIRED - The unique id for the meeting
573
            'password' => $this->getModMeetingPassword()
574
            //  -- REQUIRED - The moderator password for the meeting
575
        );
576
577
        $meetingInfoExists = false;
578
        $meetingIsRunningInfo = $this->getMeetingInfo($params);
579
        if ($this->debug) {
580
            error_log('Searching meeting with params:');
581
            error_log(print_r($params, 1));
582
            error_log('Result:');
583
            error_log(print_r($meetingIsRunningInfo, 1));
584
        }
585
586
        if ($meetingIsRunningInfo === false) {
587
            // checking with the remote_id didn't work, so just in case and
588
            // to provide backwards support, check with the id
589
            $params = array(
590
                'meetingId' => $meetingData['id'],
591
                //  -- REQUIRED - The unique id for the meeting
592
                'password' => $this->getModMeetingPassword()
593
                //  -- REQUIRED - The moderator password for the meeting
594
            );
595
            $meetingIsRunningInfo = $this->getMeetingInfo($params);
596
            if ($this->debug) {
597
                error_log('Searching meetingId with params:');
598
                error_log(print_r($params, 1));
599
                error_log('Result:');
600
                error_log(print_r($meetingIsRunningInfo, 1));
601
            }
602
        }
603
604
        if (strval($meetingIsRunningInfo['returncode']) === 'SUCCESS' &&
605
            isset($meetingIsRunningInfo['meetingName']) &&
606
            !empty($meetingIsRunningInfo['meetingName'])
607
        ) {
608
            $meetingInfoExists = true;
609
        }
610
611
        if ($this->debug) {
612
            error_log(
613
                "meeting is running: ".intval($meetingInfoExists)
614
            );
615
        }
616
617
        $url = false;
618
        if ($meetingInfoExists) {
619
            $joinParams = [
620
                'meetingId' => $meetingData['remote_id'],
621
                //	-- REQUIRED - A unique id for the meeting
622
                'username' => $this->userCompleteName,
623
                //-- REQUIRED - The name that will display for the user in the meeting
624
                'password' => $pass,
625
                //-- REQUIRED - The attendee or moderator password, depending on what's passed here
626
                //'createTime' => api_get_utc_datetime(),			//-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
627
                'userID' => api_get_user_id(),
628
                //-- OPTIONAL - string
629
                'webVoiceConf' => '',
630
            ];
631
            $url = $this->api->getJoinMeetingURL($joinParams);
632
            $url = $this->protocol.$url;
633
        }
634
635
        if ($this->debug) {
636
            error_log("return url :".$url);
637
        }
638
639
        return $url;
640
    }
641
642
    /**
643
     * Checks whether a user is teacher in the current course
644
     * @return bool True if the user can be considered a teacher in this course, false otherwise
645
     */
646
    public function isConferenceManager()
647
    {
648
        if (api_is_coach() || api_is_platform_admin(false, true)) {
649
            return true;
650
        }
651
652
        if ($this->isGlobalConferencePerUserEnabled()) {
653
            $currentUserId = api_get_user_id();
654
            if ($this->userId === $currentUserId) {
655
                return true;
656
            } else {
657
                return false;
658
            }
659
        }
660
661
        $courseInfo = api_get_course_info();
662
        $groupId = api_get_group_id();
663
        if (!empty($groupId) && !empty($courseInfo)) {
664
            $groupEnabled = api_get_course_plugin_setting('bbb', 'bbb_enable_conference_in_groups') === '1';
665
            if ($groupEnabled) {
666
                $studentCanStartConference = api_get_course_plugin_setting(
667
                        'bbb',
668
                        'big_blue_button_students_start_conference_in_groups'
669
                    ) === '1';
670
671
                if ($studentCanStartConference) {
672
                    $isSubscribed = GroupManager::is_user_in_group(
673
                        api_get_user_id(),
674
                        GroupManager::get_group_properties($groupId)
675
                    );
676
                    if ($isSubscribed) {
677
                        return true;
678
                    }
679
                }
680
            }
681
        }
682
683
        if (!empty($courseInfo)) {
684
            return api_is_course_admin();
685
        }
686
687
        return false;
688
    }
689
690
    /**
691
     * Get information about the given meeting
692
     *
693
     * @param array ...?
694
     *
695
     * @return mixed Array of information on success, false on error
696
     * @assert (array()) === false
697
     */
698
    public function getMeetingInfo($params)
699
    {
700
        try {
701
            $result = $this->api->getMeetingInfoWithXmlResponseArray($params);
702
            if ($result == null) {
703
                if ($this->debug) {
704
                    error_log("Failed to get any response. Maybe we can't contact the BBB server.");
705
                }
706
            }
707
708
            return $result;
709
        } catch (Exception $e) {
710
            if ($this->debug) {
711
                error_log('Caught exception: ', $e->getMessage(), "\n");
712
            }
713
        }
714
715
        return false;
716
    }
717
718
719
    /**
720
     * @param int $meetingId
721
     * @param int $userId
722
     *
723
     * @return array
724
     */
725
    public function getMeetingParticipantInfo($meetingId, $userId)
726
    {
727
        $meetingData = Database::select(
728
            '*',
729
            'plugin_bbb_room',
730
            array('where' => array('meeting_id = ? AND participant_id = ?' => [$meetingId, $userId])),
731
            'first'
732
        );
733
734
        if ($meetingData) {
735
            return $meetingData;
736
        }
737
738
        return [];
739
    }
740
741
    /**
742
     * Save a participant in a meeting room
743
     *
744
     * @param int $meetingId
745
     * @param int $participantId
746
     *
747
     * @return false|int The last inserted ID. Otherwise return false
748
     */
749
    public function saveParticipant($meetingId, $participantId)
750
    {
751
        $meetingData = Database::select(
752
            '*',
753
            'plugin_bbb_room',
754
            [
755
                'where' => [
756
                    'meeting_id = ? AND participant_id = ? AND close = ?' => [
757
                        $meetingId,
758
                        $participantId,
759
                        BBBPlugin::ROOM_OPEN,
760
                    ],
761
                ],
762
            ]
763
        );
764
765
        foreach ($meetingData as $roomItem) {
766
            $inAt = $roomItem['in_at'];
767
            $outAt = $roomItem['out_at'];
768
            $roomId = $roomItem['id'];
769
            if (!empty($roomId)) {
770
                if ($inAt != $outAt) {
771
                    Database::update(
772
                        'plugin_bbb_room',
773
                        ['close' => BBBPlugin::ROOM_CLOSE],
774
                        ['id = ? ' => $roomId]
775
                    );
776
                } else {
777
                    Database::update(
778
                        'plugin_bbb_room',
779
                        ['out_at' => api_get_utc_datetime(), 'close' => BBBPlugin::ROOM_CLOSE],
780
                        ['id = ? ' => $roomId]
781
                    );
782
                }
783
            }
784
        }
785
786
        $params = [
787
            'meeting_id' => $meetingId,
788
            'participant_id' => $participantId,
789
            'in_at' => api_get_utc_datetime(),
790
            'out_at' => api_get_utc_datetime(),
791
            'close' => BBBPlugin::ROOM_OPEN,
792
        ];
793
794
        return Database::insert(
795
            'plugin_bbb_room',
796
            $params
797
        );
798
    }
799
800
    /**
801
     * Tells whether the given meeting exists and is running
802
     * (using course code as name)
803
     *
804
     * @param string $meetingName Meeting name (usually the course code)
805
     *
806
     * @return bool True if meeting exists, false otherwise
807
     * @assert ('') === false
808
     * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
809
     */
810
    public function meetingExists($meetingName)
811
    {
812
        $meetingData = $this->getMeetingByName($meetingName);
813
814
        return !empty($meetingData);
815
    }
816
817
    /**
818
     * @param string $meetingName
819
     *
820
     * @return array
821
     */
822
    public function getMeetingByName($meetingName)
823
    {
824
        if (empty($meetingName)) {
825
            return [];
826
        }
827
828
        $courseId = api_get_course_int_id();
829
        $sessionId = api_get_session_id();
830
        $conditions = array(
831
            'where' => array(
832
                'c_id = ? AND session_id = ? AND meeting_name = ? AND status = 1 AND access_url = ?' =>
833
                    array($courseId, $sessionId, $meetingName, $this->accessUrl),
834
            ),
835
        );
836
837
        if ($this->hasGroupSupport()) {
838
            $groupId = api_get_group_id();
839
            $conditions = array(
840
                'where' => array(
841
                    'c_id = ? AND session_id = ? AND meeting_name = ? AND group_id = ? AND status = 1 AND access_url = ?' =>
842
                        array(
843
                            $courseId,
844
                            $sessionId,
845
                            $meetingName,
846
                            $groupId,
847
                            $this->accessUrl,
848
                        ),
849
                ),
850
            );
851
        }
852
853
        $meetingData = Database::select(
854
            '*',
855
            $this->table,
856
            $conditions,
857
            'first'
858
        );
859
860
        if ($this->debug) {
861
            error_log('meeting_exists '.print_r($meetingData, 1));
862
        }
863
864
        return $meetingData;
865
    }
866
867
    /**
868
     * Gets a list from the database of all meetings attached to a course with the given status
869
     * @param int $courseId
870
     * @param int $sessionId
871
     * @param int $status 0 for closed meetings, 1 for open meetings
872
     *
873
     * @return array
874
     */
875
    public function getAllMeetingsInCourse($courseId, $sessionId, $status)
876
    {
877
        $conditions = array(
878
            'where' => array(
879
                'status = ? AND c_id = ? AND session_id = ? ' => array(
880
                    $status,
881
                    $courseId,
882
                    $sessionId,
883
                ),
884
            ),
885
        );
886
887
        return Database::select(
888
            '*',
889
            $this->table,
890
            $conditions
891
        );
892
    }
893
894
    /**
895
     * Gets all the course meetings saved in the plugin_bbb_meeting table and
896
     * generate actionable links (join/close/delete/etc)
897
     *
898
     * @param int   $courseId
899
     * @param int   $sessionId
900
     * @param int   $groupId
901
     * @param bool  $isAdminReport Optional. Set to true then the report is for admins
902
     * @param array $dateRange     Optional
903
     *
904
     * @return array Array of current open meeting rooms
905
     * @throws Exception
906
     */
907
    public function getMeetings(
908
        $courseId = 0,
909
        $sessionId = 0,
910
        $groupId = 0,
911
        $isAdminReport = false,
912
        $dateRange = []
913
    ) {
914
        $em = Database::getManager();
915
        $manager = $this->isConferenceManager();
916
917
        $conditions = [];
918
        if ($courseId || $sessionId || $groupId) {
919
            $conditions = array(
920
                'where' => array(
921
                    'c_id = ? AND session_id = ? ' => array($courseId, $sessionId),
922
                ),
923
            );
924
925
            if ($this->hasGroupSupport()) {
926
                $conditions = array(
927
                    'where' => array(
928
                        'c_id = ? AND session_id = ? AND group_id = ? ' => array(
929
                            $courseId,
930
                            $sessionId,
931
                            $groupId,
932
                        ),
933
                    ),
934
                );
935
            }
936
937
            if ($this->isGlobalConferencePerUserEnabled()) {
938
                $conditions = array(
939
                    'where' => array(
940
                        'c_id = ? AND session_id = ? AND user_id = ?' => array(
941
                            $courseId,
942
                            $sessionId,
943
                            $this->userId,
944
                        ),
945
                    ),
946
                );
947
            }
948
        }
949
950
        if (!empty($dateRange)) {
951
            $dateStart = date_create($dateRange['search_meeting_start']);
952
            $dateStart = date_format($dateStart, 'Y-m-d H:i:s');
953
            $dateEnd = date_create($dateRange['search_meeting_end']);
954
            $dateEnd = $dateEnd->add(new DateInterval('P1D'));
955
            $dateEnd = date_format($dateEnd, 'Y-m-d H:i:s');
956
957
            $conditions = array(
958
                'where' => array(
959
                    'created_at BETWEEN ? AND ? ' => array($dateStart, $dateEnd),
960
                ),
961
            );
962
        }
963
964
        $conditions['order'] = 'created_at ASC';
965
966
        $meetingList = Database::select(
967
            '*',
968
            $this->table,
969
            $conditions
970
        );
971
        $isGlobal = $this->isGlobalConference();
972
        $newMeetingList = array();
973
        foreach ($meetingList as $meetingDB) {
974
            $item = array();
975
            $courseId = $meetingDB['c_id'];
976
            $courseInfo = api_get_course_info_by_id($courseId);
977
            $courseCode = '';
978
            if (!empty($courseInfo)) {
979
                $courseCode = $courseInfo['code'];
980
            }
981
982
            if ($manager) {
983
                $pass = $meetingDB['moderator_pw'];
984
            } else {
985
                $pass = $meetingDB['attendee_pw'];
986
            }
987
988
            $meetingBBB = $this->getMeetingInfo(
989
                [
990
                    'meetingId' => $meetingDB['remote_id'],
991
                    'password' => $pass,
992
                ]
993
            );
994
995
            if ($meetingBBB === false) {
996
                // Checking with the remote_id didn't work, so just in case and
997
                // to provide backwards support, check with the id
998
                $params = array(
999
                    'meetingId' => $meetingDB['id'],
1000
                    //  -- REQUIRED - The unique id for the meeting
1001
                    'password' => $pass
1002
                    //  -- REQUIRED - The moderator password for the meeting
1003
                );
1004
                $meetingBBB = $this->getMeetingInfo($params);
1005
            }
1006
1007
            if ($meetingDB['visibility'] == 0 && $this->isConferenceManager() === false) {
1008
                continue;
1009
            }
1010
1011
            $meetingBBB['end_url'] = $this->endUrl($meetingDB);
1012
1013
            if (isset($meetingBBB['returncode']) && (string) $meetingBBB['returncode'] === 'FAILED') {
1014
                if ($meetingDB['status'] == 1 && $this->isConferenceManager()) {
1015
                    $this->endMeeting($meetingDB['id'], $courseCode);
1016
                }
1017
            } else {
1018
                $meetingBBB['add_to_calendar_url'] = $this->addToCalendarUrl($meetingDB);
1019
            }
1020
1021
            if ($meetingDB['record'] == 1) {
1022
                // backwards compatibility (when there was no remote ID)
1023
                $mId = $meetingDB['remote_id'];
1024
                if (empty($mId)) {
1025
                    $mId = $meetingDB['id'];
1026
                }
1027
                if (empty($mId)) {
1028
                    // if the id is still empty (should *never* occur as 'id' is
1029
                    // the table's primary key), skip this conference
1030
                    continue;
1031
                }
1032
1033
                $record = [];
1034
                $recordingParams = ['meetingId' => $mId];
1035
                $records = $this->api->getRecordingsWithXmlResponseArray($recordingParams);
1036
1037
                if (!empty($records)) {
1038
                    if (!isset($records['messageKey']) || $records['messageKey'] !== 'noRecordings') {
1039
                        $record = end($records);
1040
                        if (!is_array($record) || !isset($record['recordId'])) {
1041
                            continue;
1042
                        }
1043
1044
                        if (!empty($record['playbackFormatUrl'])) {
1045
                            $this->updateMeetingVideoUrl($meetingDB['id'], $record['playbackFormatUrl']);
1046
                        }
1047
                    }
1048
                }
1049
1050
                if (isset($record['playbackFormatUrl']) && !empty($record['playbackFormatUrl'])) {
1051
                    $recordLink = Display::url(
1052
                        $this->plugin->get_lang('ViewRecord'),
1053
                        $record['playbackFormatUrl'],
1054
                        ['target' => '_blank', 'class' => 'btn btn-default']
1055
                    );
1056
                } else {
1057
                    $recordLink = $this->plugin->get_lang('NoRecording');
1058
                }
1059
1060
                if ($isAdminReport) {
1061
                    $this->forceCIdReq(
1062
                        $courseInfo['code'],
1063
                        $meetingDB['session_id'],
1064
                        $meetingDB['group_id']
1065
                    );
1066
                }
1067
1068
                $actionLinks = $this->getActionLinks(
1069
                    $meetingDB,
1070
                    $record,
1071
                    $isGlobal,
1072
                    $isAdminReport
1073
                );
1074
                $item['show_links'] = $recordLink;
1075
            } else {
1076
                $actionLinks = $this->getActionLinks(
1077
                    $meetingDB,
1078
                    [],
1079
                    $isGlobal,
1080
                    $isAdminReport
1081
                );
1082
1083
                $item['show_links'] = $this->plugin->get_lang('NoRecording');
1084
            }
1085
1086
            $item['action_links'] = implode(PHP_EOL, $actionLinks);
1087
            $item['created_at'] = api_convert_and_format_date($meetingDB['created_at']);
1088
            // created_at
1089
            $meetingDB['created_at'] = $item['created_at']; //avoid overwrite in array_merge() below
1090
1091
            $item['closed_at'] = '';
1092
            if (!empty($meetingDB['closed_at'])) {
1093
                $item['closed_at'] = api_convert_and_format_date($meetingDB['closed_at']);
1094
                $meetingDB['closed_at'] = $item['closed_at'];
1095
            }
1096
1097
            $item['publish_url'] = $this->publishUrl($meetingDB);
1098
            $item['unpublish_url'] = $this->unPublishUrl($meetingBBB);
1099
1100
            if ($meetingDB['status'] == 1) {
1101
                $joinParams = [
1102
                    'meetingId' => $meetingDB['remote_id'],
1103
                    //-- REQUIRED - A unique id for the meeting
1104
                    'username' => $this->userCompleteName,
1105
                    //-- REQUIRED - The name that will display for the user in the meeting
1106
                    'password' => $pass,
1107
                    //-- REQUIRED - The attendee or moderator password, depending on what's passed here
1108
                    'createTime' => '',
1109
                    //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
1110
                    'userID' => '',
1111
                    //	-- OPTIONAL - string
1112
                    'webVoiceConf' => '',
1113
                ];
1114
                $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
1115
            }
1116
            $item = array_merge($item, $meetingDB, $meetingBBB);
1117
1118
            $item['course'] = $em->find('ChamiloCoreBundle:Course', $item['c_id']);
1119
            $item['session'] = $em->find('ChamiloCoreBundle:Session', $item['session_id']);
1120
            $newMeetingList[] = $item;
1121
        }
1122
1123
        return $newMeetingList;
1124
    }
1125
1126
    /**
1127
     * @param array $meeting
1128
     *
1129
     * @return string
1130
     */
1131
    public function endUrl($meeting)
1132
    {
1133
        if (!isset($meeting['id'])) {
1134
            return '';
1135
        }
1136
1137
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=end&id='.$meeting['id'];
1138
    }
1139
1140
    /**
1141
     * Closes a meeting (usually when the user click on the close button from
1142
     * the conferences listing.
1143
     *
1144
     * @param string The internal ID of the meeting (id field for this meeting)
1145
     * @param string $courseCode
1146
     *
1147
     * @return void
1148
     * @assert (0) === false
1149
     */
1150
    public function endMeeting($id, $courseCode = null)
1151
    {
1152
        if (empty($id)) {
1153
            return false;
1154
        }
1155
1156
        $meetingData = Database::select(
1157
            '*',
1158
            $this->table,
1159
            array('where' => array('id = ?' => array($id))),
1160
            'first'
1161
        );
1162
        $manager = $this->isConferenceManager();
1163
        if ($manager) {
1164
            $pass = $meetingData['moderator_pw'];
1165
        } else {
1166
            $pass = $meetingData['attendee_pw'];
1167
        }
1168
1169
        Event::addEvent(
1170
            'bbb_end_meeting',
1171
            'meeting_id',
1172
            (int) $id,
1173
            null,
1174
            api_get_user_id(),
1175
            api_get_course_int_id(),
1176
            api_get_session_id()
1177
        );
1178
1179
        $endParams = array(
1180
            'meetingId' => $meetingData['remote_id'], // REQUIRED - We have to know which meeting to end.
1181
            'password' => $pass, // REQUIRED - Must match moderator pass for meeting.
1182
        );
1183
        $this->api->endMeetingWithXmlResponseArray($endParams);
1184
        Database::update(
1185
            $this->table,
1186
            array('status' => 0, 'closed_at' => api_get_utc_datetime()),
1187
            array('id = ? ' => $id)
1188
        );
1189
1190
        // Update users with in_at y ou_at field equal
1191
        $roomTable = Database::get_main_table('plugin_bbb_room');
1192
        $conditions['where'] = ['meeting_id=? AND in_at=out_at AND close=?' => [$id, BBBPlugin::ROOM_OPEN]];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$conditions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $conditions = array(); before regardless.
Loading history...
1193
        $roomList = Database::select(
1194
            '*',
1195
            $roomTable,
1196
            $conditions
1197
        );
1198
1199
        foreach ($roomList as $roomDB) {
1200
            $roomId = $roomDB['id'];
1201
            if (!empty($roomId)) {
1202
                Database::update(
1203
                    $roomTable,
1204
                    ['out_at' => api_get_utc_datetime(), 'close' => BBBPlugin::ROOM_CLOSE],
1205
                    ['id = ? ' => $roomId]
1206
                );
1207
            }
1208
        }
1209
1210
        // Close all meeting rooms with meeting ID
1211
        Database::update(
1212
            $roomTable,
1213
            ['close' => BBBPlugin::ROOM_CLOSE],
1214
            ['meeting_id = ? ' => $id]
1215
        );
1216
    }
1217
1218
    /**
1219
     * @param array $meeting
1220
     * @param array $record
1221
     *
1222
     * @return string
1223
     */
1224
    public function addToCalendarUrl($meeting, $record = [])
1225
    {
1226
        $url = isset($record['playbackFormatUrl']) ? $record['playbackFormatUrl'] : '';
1227
1228
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1229
            ).'&action=add_to_calendar&id='.$meeting['id'].'&start='.api_strtotime($meeting['created_at']).'&url='.$url;
1230
    }
1231
1232
    /**
1233
     * @param int    $meetingId
1234
     * @param string $videoUrl
1235
     *
1236
     * @return bool|int
1237
     */
1238
    public function updateMeetingVideoUrl($meetingId, $videoUrl)
1239
    {
1240
        return Database::update(
1241
            'plugin_bbb_meeting',
1242
            ['video_url' => $videoUrl],
1243
            ['id = ?' => intval($meetingId)]
1244
        );
1245
    }
1246
1247
    /**
1248
     * Force the course, session and/or group IDs
1249
     *
1250
     * @param string $courseCode
1251
     * @param int    $sessionId
1252
     * @param int    $groupId
1253
     */
1254
    public function forceCIdReq($courseCode, $sessionId = 0, $groupId = 0)
1255
    {
1256
        $this->courseCode = $courseCode;
1257
        $this->sessionId = (int) $sessionId;
1258
        $this->groupId = (int) $groupId;
1259
    }
1260
1261
    /**
1262
     * @param array $meetingInfo
1263
     * @param array $recordInfo
1264
     * @param bool  $isGlobal
1265
     * @param bool  $isAdminReport
1266
     *
1267
     * @return array
1268
     */
1269
    private function getActionLinks(
1270
        $meetingInfo,
1271
        $recordInfo,
1272
        $isGlobal = false,
1273
        $isAdminReport = false
1274
    ) {
1275
        $isVisible = $meetingInfo['visibility'] != 0;
1276
        $linkVisibility = $isVisible
1277
            ? Display::url(
1278
                Display::return_icon('visible.png', get_lang('MakeInvisible')),
1279
                $this->unPublishUrl($meetingInfo)
1280
            )
1281
            : Display::url(
1282
                Display::return_icon('invisible.png', get_lang('MakeVisible')),
1283
                $this->publishUrl($meetingInfo)
1284
            );
1285
1286
        $links = [];
1287
        if ($this->plugin->get('allow_regenerate_recording') === 'true' && $meetingInfo['record'] == 1) {
1288
            if (!empty($recordInfo)) {
1289
                $links[] = Display::url(
1290
                    Display::return_icon('reload.png', get_lang('RegenerateRecord')),
1291
                    $this->regenerateRecordUrl($meetingInfo, $recordInfo)
1292
                );
1293
            } else {
1294
                $links[] = Display::url(
1295
                    Display::return_icon('reload.png', get_lang('RegenerateRecord')),
1296
                    $this->regenerateRecordUrlFromMeeting($meetingInfo)
1297
                );
1298
            }
1299
        }
1300
1301
        if (empty($recordInfo)) {
1302
            if (!$isAdminReport) {
1303
                if ($meetingInfo['status'] == 0) {
1304
                    $links[] = Display::url(
1305
                        Display::return_icon('delete.png', get_lang('Delete')),
1306
                        $this->deleteRecordUrl($meetingInfo)
1307
                    );
1308
                    $links[] = $linkVisibility;
1309
                }
1310
1311
                return $links;
1312
            } else {
1313
                $links[] = Display::url(
1314
                    Display::return_icon('course_home.png', get_lang('GoToCourse')),
1315
                    $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1316
                );
1317
1318
                return $links;
1319
            }
1320
        }
1321
1322
        if (!$isGlobal) {
1323
            $links[] = Display::url(
1324
                Display::return_icon('link.gif', get_lang('UrlMeetingToShare')),
1325
                $this->copyToRecordToLinkTool($meetingInfo)
1326
            );
1327
            $links[] = Display::url(
1328
                Display::return_icon('agenda.png', get_lang('AddToCalendar')),
1329
                $this->addToCalendarUrl($meetingInfo, $recordInfo)
1330
            );
1331
        }
1332
1333
        $hide = $this->plugin->get('disable_download_conference_link') === 'true' ? true : false;
1334
1335
        if ($hide == false) {
1336
            if ($meetingInfo['has_video_m4v']) {
1337
                $links[] = Display::url(
1338
                    Display::return_icon('save.png', get_lang('DownloadFile')),
1339
                    $recordInfo['playbackFormatUrl'].'/capture.m4v',
1340
                    ['target' => '_blank']
1341
                );
1342
            } else {
1343
                $links[] = Display::url(
1344
                    Display::return_icon('save.png', get_lang('DownloadFile')),
1345
                    '#',
1346
                    [
1347
                        'id' => "btn-check-meeting-video-{$meetingInfo['id']}",
1348
                        'class' => 'check-meeting-video',
1349
                        'data-id' => $meetingInfo['id'],
1350
                    ]
1351
                );
1352
            }
1353
        }
1354
1355
1356
        if (!$isAdminReport) {
1357
            $links[] = Display::url(
1358
                Display::return_icon('delete.png', get_lang('Delete')),
1359
                $this->deleteRecordUrl($meetingInfo)
1360
            );
1361
            $links[] = $linkVisibility;
1362
        } else {
1363
            $links[] = Display::url(
1364
                Display::return_icon('course_home.png', get_lang('GoToCourse')),
1365
                $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1366
            );
1367
        }
1368
1369
1370
        return $links;
1371
    }
1372
1373
    /**
1374
     * @param array $meeting
1375
     *
1376
     * @return string
1377
     */
1378
    public function unPublishUrl($meeting)
1379
    {
1380
        if (!isset($meeting['id'])) {
1381
            return null;
1382
        }
1383
1384
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1385
            ).'&action=unpublish&id='.$meeting['id'];
1386
    }
1387
1388
    /**
1389
     * @param array $meeting
1390
     *
1391
     * @return string
1392
     */
1393
    public function publishUrl($meeting)
1394
    {
1395
        if (!isset($meeting['id'])) {
1396
            return '';
1397
        }
1398
1399
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1400
            ).'&action=publish&id='.$meeting['id'];
1401
    }
1402
1403
    /**
1404
     * @param array $meeting
1405
     * @param array $recordInfo
1406
     *
1407
     * @return string
1408
     */
1409
    public function regenerateRecordUrl($meeting, $recordInfo)
1410
    {
1411
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1412
            return '';
1413
        }
1414
1415
        if (!isset($meeting['id'])) {
1416
            return '';
1417
        }
1418
1419
        if (empty($recordInfo) || (!empty($recordInfo['recordId']) && !isset($recordInfo['recordId']))) {
1420
            return '';
1421
        }
1422
1423
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().
1424
            '&action=regenerate_record&id='.$meeting['id'].'&record_id='.$recordInfo['recordId'];
1425
    }
1426
1427
    /**
1428
     * @param array $meeting
1429
     *
1430
     * @return string
1431
     */
1432
    public function regenerateRecordUrlFromMeeting($meeting)
1433
    {
1434
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1435
            return '';
1436
        }
1437
1438
        if (!isset($meeting['id'])) {
1439
            return '';
1440
        }
1441
1442
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().
1443
            '&action=regenerate_record&id='.$meeting['id'];
1444
    }
1445
1446
    /**
1447
     * @param array $meeting
1448
     *
1449
     * @return string
1450
     */
1451
    public function deleteRecordUrl($meeting)
1452
    {
1453
        if (!isset($meeting['id'])) {
1454
            return '';
1455
        }
1456
1457
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1458
            ).'&action=delete_record&id='.$meeting['id'];
1459
    }
1460
1461
    /**
1462
     * @param array $meeting
1463
     *
1464
     * @return string
1465
     */
1466
    public function copyToRecordToLinkTool($meeting)
1467
    {
1468
        if (!isset($meeting['id'])) {
1469
            return '';
1470
        }
1471
1472
        return api_get_path(WEB_PLUGIN_PATH).
1473
            'bbb/listing.php?'.$this->getUrlParams().'&action=copy_record_to_link_tool&id='.$meeting['id'];
1474
    }
1475
1476
    /**
1477
     * Function disabled
1478
     */
1479
    public function publishMeeting($id)
1480
    {
1481
        //return BigBlueButtonBN::setPublishRecordings($id, 'true', $this->url, $this->salt);
1482
        if (empty($id)) {
1483
            return false;
1484
        }
1485
        $id = intval($id);
1486
        Database::update($this->table, array('visibility' => 1), array('id = ? ' => $id));
1487
1488
        return true;
1489
    }
1490
1491
    /**
1492
     * Function disabled
1493
     */
1494
    public function unpublishMeeting($id)
1495
    {
1496
        //return BigBlueButtonBN::setPublishRecordings($id, 'false', $this->url, $this->salt);
1497
        if (empty($id)) {
1498
            return false;
1499
        }
1500
        $id = intval($id);
1501
        Database::update($this->table, array('visibility' => 0), array('id = ?' => $id));
1502
1503
        return true;
1504
    }
1505
1506
    /**
1507
     * Get users online in the current course room.
1508
     *
1509
     * @return int The number of users currently connected to the videoconference
1510
     * @assert () > -1
1511
     */
1512
    public function getUsersOnlineInCurrentRoom()
1513
    {
1514
        $courseId = api_get_course_int_id();
1515
        $sessionId = api_get_session_id();
1516
1517
        $conditions = array(
1518
            'where' => array(
1519
                'c_id = ? AND session_id = ? AND status = 1 AND access_url = ?' => array(
1520
                    $courseId,
1521
                    $sessionId,
1522
                    $this->accessUrl,
1523
                ),
1524
            ),
1525
        );
1526
1527
        if ($this->hasGroupSupport()) {
1528
            $groupId = api_get_group_id();
1529
            $conditions = array(
1530
                'where' => array(
1531
                    'c_id = ? AND session_id = ? AND group_id = ? AND status = 1 AND access_url = ?' => array(
1532
                        $courseId,
1533
                        $sessionId,
1534
                        $groupId,
1535
                        $this->accessUrl,
1536
                    ),
1537
                ),
1538
            );
1539
        }
1540
1541
        if ($this->isGlobalConferencePerUserEnabled()) {
1542
            $conditions = array(
1543
                'where' => array(
1544
                    'user_id = ? AND status = 1 AND access_url = ?' => array(
1545
                        $this->userId,
1546
                        $this->accessUrl,
1547
                    ),
1548
                ),
1549
            );
1550
        }
1551
1552
        $meetingData = Database::select(
1553
            '*',
1554
            $this->table,
1555
            $conditions,
1556
            'first'
1557
        );
1558
1559
        if (empty($meetingData)) {
1560
            return 0;
1561
        }
1562
        $pass = $meetingData['moderator_pw'];
1563
        $info = $this->getMeetingInfo(array('meetingId' => $meetingData['remote_id'], 'password' => $pass));
1564
        if ($info === false) {
1565
            //checking with the remote_id didn't work, so just in case and
1566
            // to provide backwards support, check with the id
1567
            $params = array(
1568
                'meetingId' => $meetingData['id'],
1569
                //  -- REQUIRED - The unique id for the meeting
1570
                'password' => $pass
1571
                //  -- REQUIRED - The moderator password for the meeting
1572
            );
1573
            $info = $this->getMeetingInfo($params);
1574
        }
1575
1576
        if (!empty($info) && isset($info['participantCount'])) {
1577
            return $info['participantCount'];
1578
        }
1579
1580
        return 0;
1581
    }
1582
1583
    /**
1584
     * @param int    $id
1585
     * @param string $recordId
1586
     *
1587
     * @return bool
1588
     */
1589
    public function regenerateRecording($id, $recordId = '')
1590
    {
1591
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1592
            return false;
1593
        }
1594
1595
        if (empty($id)) {
1596
            return false;
1597
        }
1598
1599
        $meetingData = Database::select(
1600
            '*',
1601
            $this->table,
1602
            array('where' => array('id = ?' => array($id))),
1603
            'first'
1604
        );
1605
1606
        Event::addEvent(
1607
            'bbb_regenerate_record',
1608
            'record_id',
1609
            (int) $recordId,
1610
            null,
1611
            api_get_user_id(),
1612
            api_get_course_int_id(),
1613
            api_get_session_id()
1614
        );
1615
1616
        // Check if there are recordings for this meeting
1617
        $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
1618
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] === 'noRecordings') {
1619
            // Regenerate the meeting id
1620
            if (!empty($meetingData['internal_meeting_id'])) {
1621
                return $this->api->generateRecording(['recordId' => $meetingData['internal_meeting_id']]);
1622
            }
1623
1624
            /*$pass = $this->getModMeetingPassword();
1625
            $info = $this->getMeetingInfo(['meetingId' => $meetingData['remote_id'], 'password' => $pass]);
1626
            if (!empty($info) && isset($info['internalMeetingID'])) {
1627
                return $this->api->generateRecording(['recordId' => $meetingData['internal_meeting_id']]);
1628
            }*/
1629
1630
            return false;
1631
        } else {
1632
            if (!empty($recordings['records'])) {
1633
                $recordExists = false;
1634
                foreach ($recordings['records'] as $record) {
1635
                    if ($recordId == $record['recordId']) {
1636
                        $recordExists = true;
1637
                        break;
1638
                    }
1639
                }
1640
1641
                if ($recordExists) {
1642
                    return $this->api->generateRecording(['recordId' => $recordId]);
1643
                }
1644
            }
1645
        }
1646
1647
        return false;
1648
    }
1649
1650
    /**
1651
     * Deletes a recording of a meeting
1652
     *
1653
     * @param int $id ID of the recording
1654
     *
1655
     * @return bool
1656
     *
1657
     * @assert () === false
1658
     * @todo Also delete links and agenda items created from this recording
1659
     */
1660
    public function deleteRecording($id)
1661
    {
1662
        if (empty($id)) {
1663
            return false;
1664
        }
1665
1666
        $meetingData = Database::select(
1667
            '*',
1668
            $this->table,
1669
            array('where' => array('id = ?' => array($id))),
1670
            'first'
1671
        );
1672
1673
        Event::addEvent(
1674
            'bbb_delete_record',
1675
            'meeting_id',
1676
            $id,
1677
            null,
1678
            api_get_user_id(),
1679
            api_get_course_int_id(),
1680
            api_get_session_id()
1681
        );
1682
1683
        $delete = false;
1684
        $recordings = [];
1685
        // Check if there are recordings for this meeting
1686
        if (!empty($meetingData['remote_id'])) {
1687
            Event::addEvent(
1688
                'bbb_delete_record',
1689
                'remote_id',
1690
                $meetingData['remote_id'],
1691
                null,
1692
                api_get_user_id(),
1693
                api_get_course_int_id(),
1694
                api_get_session_id()
1695
            );
1696
            $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
1697
        }
1698
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] == 'noRecordings') {
1699
            $delete = true;
1700
        } else {
1701
            if (!empty($recordings['records'])) {
1702
                $recordsToDelete = [];
1703
                foreach ($recordings['records'] as $record) {
1704
                    $recordsToDelete[] = $record['recordId'];
1705
                }
1706
                $delete = true;
1707
                if (!empty($recordsToDelete)) {
1708
                    $recordingParams = ['recordId' => implode(',', $recordsToDelete)];
1709
                    Event::addEvent(
1710
                        'bbb_delete_record',
1711
                        'record_id_list',
1712
                        implode(',', $recordsToDelete),
1713
                        null,
1714
                        api_get_user_id(),
1715
                        api_get_course_int_id(),
1716
                        api_get_session_id()
1717
                    );
1718
                    $result = $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
1719
                    if (!empty($result) && isset($result['deleted']) && $result['deleted'] === 'true') {
1720
                        $delete = true;
1721
                    }
1722
                }
1723
            }
1724
        }
1725
1726
        if ($delete) {
1727
            Database::delete(
1728
                'plugin_bbb_room',
1729
                array('meeting_id = ?' => array($id))
1730
            );
1731
1732
            Database::delete(
1733
                $this->table,
1734
                array('id = ?' => array($id))
1735
            );
1736
        }
1737
1738
        return $delete;
1739
    }
1740
1741
    /**
1742
     * Creates a link in the links tool from the given videoconference recording
1743
     *
1744
     * @param int $id ID of the item in the plugin_bbb_meeting table
1745
     * @param string Hash identifying the recording, as provided by the API
1746
     *
1747
     * @return mixed ID of the newly created link, or false on error
1748
     * @assert (null, null) === false
1749
     * @assert (1, null) === false
1750
     * @assert (null, 'abcdefabcdefabcdefabcdef') === false
1751
     */
1752
    public function copyRecordingToLinkTool($id)
1753
    {
1754
        if (empty($id)) {
1755
            return false;
1756
        }
1757
        //$records =  BigBlueButtonBN::getRecordingsUrl($id);
1758
        $meetingData = Database::select(
1759
            '*',
1760
            $this->table,
1761
            array('where' => array('id = ?' => array($id))),
1762
            'first'
1763
        );
1764
1765
        $records = $this->api->getRecordingsWithXmlResponseArray(
1766
            array('meetingId' => $meetingData['remote_id'])
1767
        );
1768
1769
        if (!empty($records)) {
1770
            if (isset($records['message']) && !empty($records['message'])) {
1771
                if ($records['messageKey'] == 'noRecordings') {
1772
                    $recordArray[] = $this->plugin->get_lang('NoRecording');
1773
                } else {
1774
                    //$recordArray[] = $records['message'];
1775
                }
1776
1777
                return false;
1778
            } else {
1779
                $record = $records[0];
1780
                if (is_array($record) && isset($record['recordId'])) {
1781
                    $url = $record['playbackFormatUrl'];
1782
                    $link = new Link();
1783
                    $params['url'] = $url;
1784
                    $params['title'] = $meetingData['meeting_name'];
1785
                    $id = $link->save($params);
1786
1787
                    return $id;
1788
                }
1789
            }
1790
        }
1791
1792
        return false;
1793
    }
1794
1795
    /**
1796
     * Checks if the video conference server is running.
1797
     * Function currently disabled (always returns 1)
1798
     * @return bool True if server is running, false otherwise
1799
     * @assert () === false
1800
     */
1801
    public function isServerRunning()
1802
    {
1803
        return true;
1804
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1805
    }
1806
1807
    /**
1808
     * Checks if the video conference plugin is properly configured
1809
     * @return bool True if plugin has a host and a salt, false otherwise
1810
     * @assert () === false
1811
     */
1812
    public function isServerConfigured()
1813
    {
1814
        $host = $this->plugin->get('host');
1815
1816
        if (empty($host)) {
1817
            return false;
1818
        }
1819
1820
        $salt = $this->plugin->get('salt');
1821
1822
        if (empty($salt)) {
1823
            return false;
1824
        }
1825
1826
        return true;
1827
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1828
    }
1829
1830
    /**
1831
     * Get active session in the all platform
1832
     */
1833
    public function getActiveSessionsCount()
1834
    {
1835
        $meetingList = Database::select(
1836
            'count(id) as count',
1837
            $this->table,
1838
            array('where' => array('status = ? AND access_url = ?' => array(1, $this->accessUrl))),
1839
            'first'
1840
        );
1841
1842
        return $meetingList['count'];
1843
    }
1844
1845
    /**
1846
     * Get active session in the all platform
1847
     */
1848
    public function getActiveSessions()
1849
    {
1850
        $meetingList = Database::select(
1851
            '*',
1852
            $this->table,
1853
            array('where' => array('status = ? AND access_url = ?' => array(1, $this->accessUrl)))
1854
        );
1855
1856
        return $meetingList;
1857
    }
1858
1859
    /**
1860
     * @param string $url
1861
     */
1862
    public function redirectToBBB($url)
1863
    {
1864
        if (file_exists(__DIR__.'/../config.vm.php')) {
1865
            // Using VM
1866
            echo Display::url($this->plugin->get_lang('ClickToContinue'), $url);
1867
            exit;
1868
        } else {
1869
            // Classic
1870
            header("Location: $url");
1871
            exit;
1872
        }
1873
    }
1874
1875
    /**
1876
     * @return string
1877
     */
1878
    public function getConferenceUrl()
1879
    {
1880
        return api_get_path(WEB_PLUGIN_PATH).'bbb/start.php?launch=1&'.$this->getUrlParams();
1881
    }
1882
1883
    /**
1884
     * Get the meeting info from DB by its name
1885
     *
1886
     * @param string $name
1887
     *
1888
     * @return array
1889
     */
1890
    public function findMeetingByName($name)
1891
    {
1892
        $meetingData = Database::select(
1893
            '*',
1894
            'plugin_bbb_meeting',
1895
            array('where' => array('meeting_name = ? AND status = 1 ' => $name)),
1896
            'first'
1897
        );
1898
1899
        return $meetingData;
1900
    }
1901
1902
    /**
1903
     * Get the meeting info from DB by its name
1904
     *
1905
     * @param int $id
1906
     *
1907
     * @return array
1908
     */
1909
    public function getMeeting($id)
1910
    {
1911
        $meetingData = Database::select(
1912
            '*',
1913
            'plugin_bbb_meeting',
1914
            array('where' => array('id = ?' => $id)),
1915
            'first'
1916
        );
1917
1918
        return $meetingData;
1919
    }
1920
1921
    /**
1922
     * Get the meeting info.
1923
     *
1924
     * @param int $id
1925
     *
1926
     * @return array
1927
     */
1928
    public function getMeetingByRemoteId($id)
1929
    {
1930
        $meetingData = Database::select(
1931
            '*',
1932
            'plugin_bbb_meeting',
1933
            array('where' => array('remote_id = ?' => $id)),
1934
            'first'
1935
        );
1936
1937
        return $meetingData;
1938
    }
1939
1940
    /**
1941
     * @param int $meetingId
1942
     *
1943
     * @return array
1944
     */
1945
    public function findConnectedMeetingParticipants($meetingId)
1946
    {
1947
        $meetingData = Database::select(
1948
            '*',
1949
            'plugin_bbb_room',
1950
            array('where' => array('meeting_id = ? AND in_at IS NOT NULL' => $meetingId))
1951
        );
1952
        $participantIds = [];
1953
        $return = [];
1954
1955
        foreach ($meetingData as $participantInfo) {
1956
            if (in_array($participantInfo['participant_id'], $participantIds)) {
1957
                continue;
1958
            }
1959
1960
            $participantIds[] = $participantInfo['participant_id'];
1961
1962
            $return[] = [
1963
                'id' => $participantInfo['id'],
1964
                'meeting_id' => $participantInfo['meeting_id'],
1965
                'participant' => api_get_user_entity($participantInfo['participant_id']),
1966
                'in_at' => $participantInfo['in_at'],
1967
                'out_at' => $participantInfo['out_at'],
1968
            ];
1969
        }
1970
1971
        return $return;
1972
    }
1973
1974
    /**
1975
     * Check if the meeting has a capture.m4v video file. If exists then the has_video_m4v field is updated
1976
     *
1977
     * @param int $meetingId
1978
     *
1979
     * @return bool
1980
     */
1981
    public function checkDirectMeetingVideoUrl($meetingId)
1982
    {
1983
        $meetingInfo = Database::select(
1984
            '*',
1985
            'plugin_bbb_meeting',
1986
            [
1987
                'where' => ['id = ?' => intval($meetingId)],
1988
            ],
1989
            'first'
1990
        );
1991
1992
        if (!isset($meetingInfo['video_url'])) {
1993
            return false;
1994
        }
1995
1996
        $hasCapture = SocialManager::verifyUrl($meetingInfo['video_url'].'/capture.m4v');
1997
1998
        if ($hasCapture) {
1999
            return Database::update(
2000
                'plugin_bbb_meeting',
2001
                ['has_video_m4v' => true],
2002
                ['id = ?' => intval($meetingId)]
2003
            );
2004
        }
2005
2006
        return $hasCapture;
2007
    }
2008
}
2009