Passed
Pull Request — 1.11.x (#3998)
by Ghazi
09:57
created

bbb::insertMeetingFormat()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 3
dl 0
loc 11
rs 10
c 0
b 0
f 0
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');
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

56
        /** @scrutinizer ignore-call */ 
57
        $bbbPluginEnabled = $this->plugin->get('tool_enable');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
391
            Event::addEvent(
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

391
            Event::/** @scrutinizer ignore-call */ 
392
                   addEvent(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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);
0 ignored issues
show
Bug introduced by
Are you sure $url of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

636
            error_log("return url :"./** @scrutinizer ignore-type */ $url);
Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $meetingData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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));
0 ignored issues
show
Bug introduced by
Are you sure print_r($meetingData, 1) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

861
            error_log('meeting_exists './** @scrutinizer ignore-type */ print_r($meetingData, 1));
Loading history...
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['playbackFormat'])) {
1045
                            $this->updateMeetingVideoUrl($meetingDB['id'], $record['playbackFormatUrl']);
1046
                        }
1047
                    }
1048
                }
1049
1050
                if (isset($record['playbackFormat']) && !empty($record['playbackFormat'])) {
1051
                    $recordLink = array();
1052
                    foreach ($record['playbackFormat'] as $format) {
1053
                        $this->insertMeetingFormat(intval($meetingDB['id']), $format->type->__toString(), $format->url->__toString());
1054
                        $recordLink['record'][] = 1;
1055
                        $recordLink[] = Display::url(
1056
                            $this->plugin->get_lang($format->type->__toString()),
1057
                            $format->url->__toString(),
1058
                            ['target' => '_blank', 'class' => 'btn btn-default']
1059
                        );
1060
                    }
1061
                } else {
1062
                    $recordLink = $this->plugin->get_lang('NoRecording');
1063
                }
1064
1065
                if ($isAdminReport) {
1066
                    $this->forceCIdReq(
1067
                        $courseInfo['code'],
1068
                        $meetingDB['session_id'],
1069
                        $meetingDB['group_id']
1070
                    );
1071
                }
1072
1073
                $actionLinks = $this->getActionLinks(
1074
                    $meetingDB,
1075
                    $record,
1076
                    $isGlobal,
1077
                    $isAdminReport
1078
                );
1079
                $item['show_links'] = $recordLink;
1080
                $item['record'] = true;
1081
            } else {
1082
                $actionLinks = $this->getActionLinks(
1083
                    $meetingDB,
1084
                    [],
1085
                    $isGlobal,
1086
                    $isAdminReport
1087
                );
1088
1089
                $item['show_links'] = $this->plugin->get_lang('NoRecording');
1090
                $item['record'] = false;
1091
            }
1092
1093
            $item['action_links'] = implode(PHP_EOL, $actionLinks);
1094
            $item['created_at'] = api_convert_and_format_date($meetingDB['created_at']);
1095
            // created_at
1096
            $meetingDB['created_at'] = $item['created_at']; //avoid overwrite in array_merge() below
1097
1098
            $item['closed_at'] = '';
1099
            if (!empty($meetingDB['closed_at'])) {
1100
                $item['closed_at'] = api_convert_and_format_date($meetingDB['closed_at']);
1101
                $meetingDB['closed_at'] = $item['closed_at'];
1102
            }
1103
1104
            $item['publish_url'] = $this->publishUrl($meetingDB);
1105
            $item['unpublish_url'] = $this->unPublishUrl($meetingBBB);
1106
1107
            if ($meetingDB['status'] == 1) {
1108
                $joinParams = [
1109
                    'meetingId' => $meetingDB['remote_id'],
1110
                    //-- REQUIRED - A unique id for the meeting
1111
                    'username' => $this->userCompleteName,
1112
                    //-- REQUIRED - The name that will display for the user in the meeting
1113
                    'password' => $pass,
1114
                    //-- REQUIRED - The attendee or moderator password, depending on what's passed here
1115
                    'createTime' => '',
1116
                    //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
1117
                    'userID' => '',
1118
                    //	-- OPTIONAL - string
1119
                    'webVoiceConf' => '',
1120
                ];
1121
                $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
1122
            }
1123
            $item = array_merge($item, $meetingDB, $meetingBBB);
1124
1125
            $item['course'] = $em->find('ChamiloCoreBundle:Course', $item['c_id']);
1126
            $item['session'] = $em->find('ChamiloCoreBundle:Session', $item['session_id']);
1127
            $newMeetingList[] = $item;
1128
        }
1129
1130
        return $newMeetingList;
1131
    }
1132
1133
    /**
1134
     * @param array $meeting
1135
     *
1136
     * @return string
1137
     */
1138
    public function endUrl($meeting)
1139
    {
1140
        if (!isset($meeting['id'])) {
1141
            return '';
1142
        }
1143
1144
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=end&id='.$meeting['id'];
1145
    }
1146
1147
    /**
1148
     * Closes a meeting (usually when the user click on the close button from
1149
     * the conferences listing.
1150
     *
1151
     * @param string The internal ID of the meeting (id field for this meeting)
1152
     * @param string $courseCode
1153
     *
1154
     * @return void
1155
     * @assert (0) === false
1156
     */
1157
    public function endMeeting($id, $courseCode = null)
1158
    {
1159
        if (empty($id)) {
1160
            return false;
1161
        }
1162
1163
        $meetingData = Database::select(
1164
            '*',
1165
            $this->table,
1166
            array('where' => array('id = ?' => array($id))),
1167
            'first'
1168
        );
1169
        $manager = $this->isConferenceManager();
1170
        if ($manager) {
1171
            $pass = $meetingData['moderator_pw'];
1172
        } else {
1173
            $pass = $meetingData['attendee_pw'];
1174
        }
1175
1176
        Event::addEvent(
1177
            'bbb_end_meeting',
1178
            'meeting_id',
1179
            (int) $id,
1180
            null,
1181
            api_get_user_id(),
1182
            api_get_course_int_id(),
1183
            api_get_session_id()
1184
        );
1185
1186
        $endParams = array(
1187
            'meetingId' => $meetingData['remote_id'], // REQUIRED - We have to know which meeting to end.
1188
            'password' => $pass, // REQUIRED - Must match moderator pass for meeting.
1189
        );
1190
        $this->api->endMeetingWithXmlResponseArray($endParams);
1191
        Database::update(
1192
            $this->table,
1193
            array('status' => 0, 'closed_at' => api_get_utc_datetime()),
1194
            array('id = ? ' => $id)
1195
        );
1196
1197
        // Update users with in_at y ou_at field equal
1198
        $roomTable = Database::get_main_table('plugin_bbb_room');
1199
        $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...
1200
        $roomList = Database::select(
1201
            '*',
1202
            $roomTable,
1203
            $conditions
1204
        );
1205
1206
        foreach ($roomList as $roomDB) {
1207
            $roomId = $roomDB['id'];
1208
            if (!empty($roomId)) {
1209
                Database::update(
1210
                    $roomTable,
1211
                    ['out_at' => api_get_utc_datetime(), 'close' => BBBPlugin::ROOM_CLOSE],
1212
                    ['id = ? ' => $roomId]
1213
                );
1214
            }
1215
        }
1216
1217
        // Close all meeting rooms with meeting ID
1218
        Database::update(
1219
            $roomTable,
1220
            ['close' => BBBPlugin::ROOM_CLOSE],
1221
            ['meeting_id = ? ' => $id]
1222
        );
1223
    }
1224
1225
    /**
1226
     * @param array $meeting
1227
     * @param array $record
1228
     *
1229
     * @return string
1230
     */
1231
    public function addToCalendarUrl($meeting, $record = [])
1232
    {
1233
        $url = isset($record['playbackFormatUrl']) ? $record['playbackFormatUrl'] : '';
1234
1235
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1236
            ).'&action=add_to_calendar&id='.$meeting['id'].'&start='.api_strtotime($meeting['created_at']).'&url='.$url;
1237
    }
1238
1239
    /**
1240
     * @param int    $meetingId
1241
     * @param string $videoUrl
1242
     *
1243
     * @return bool|int
1244
     */
1245
    public function updateMeetingVideoUrl($meetingId, $videoUrl)
1246
    {
1247
        return Database::update(
1248
            'plugin_bbb_meeting',
1249
            ['video_url' => $videoUrl],
1250
            ['id = ?' => intval($meetingId)]
1251
        );
1252
    }
1253
1254
    /**
1255
     * @param int $meetingId
1256
     * @param string $formatType
1257
     * @param string $resourceUrl
1258
     *
1259
     * @return bool|int
1260
     */
1261
    public function insertMeetingFormat(int $meetingId, string $formatType, string $resourceUrl)
1262
    {
1263
        $em = Database::getManager();
1264
        $sm = $em->getConnection()->getSchemaManager();
1265
        if ($sm->tablesExist('plugin_bbb_meeting_format')) {
1266
            return Database::insert(
1267
                'plugin_bbb_meeting_format',
1268
                [
1269
                    'format_type' => $formatType,
1270
                    'resource_url' => $resourceUrl,
1271
                    'meeting_id' => $meetingId
1272
                ]
1273
            );
1274
        }
1275
1276
    }
1277
1278
    /**
1279
     * Force the course, session and/or group IDs
1280
     *
1281
     * @param string $courseCode
1282
     * @param int    $sessionId
1283
     * @param int    $groupId
1284
     */
1285
    public function forceCIdReq($courseCode, $sessionId = 0, $groupId = 0)
1286
    {
1287
        $this->courseCode = $courseCode;
1288
        $this->sessionId = (int) $sessionId;
1289
        $this->groupId = (int) $groupId;
1290
    }
1291
1292
    /**
1293
     * @param array $meetingInfo
1294
     * @param array $recordInfo
1295
     * @param bool  $isGlobal
1296
     * @param bool  $isAdminReport
1297
     *
1298
     * @return array
1299
     */
1300
    private function getActionLinks(
1301
        $meetingInfo,
1302
        $recordInfo,
1303
        $isGlobal = false,
1304
        $isAdminReport = false
1305
    ) {
1306
        $isVisible = $meetingInfo['visibility'] != 0;
1307
        $linkVisibility = $isVisible
1308
            ? Display::url(
1309
                Display::return_icon('visible.png', get_lang('MakeInvisible')),
1310
                $this->unPublishUrl($meetingInfo)
1311
            )
1312
            : Display::url(
1313
                Display::return_icon('invisible.png', get_lang('MakeVisible')),
1314
                $this->publishUrl($meetingInfo)
1315
            );
1316
1317
        $links = [];
1318
        if ($this->plugin->get('allow_regenerate_recording') === 'true' && $meetingInfo['record'] == 1) {
1319
            if (!empty($recordInfo)) {
1320
                $links[] = Display::url(
1321
                    Display::return_icon('reload.png', get_lang('RegenerateRecord')),
1322
                    $this->regenerateRecordUrl($meetingInfo, $recordInfo)
1323
                );
1324
            } else {
1325
                $links[] = Display::url(
1326
                    Display::return_icon('reload.png', get_lang('RegenerateRecord')),
1327
                    $this->regenerateRecordUrlFromMeeting($meetingInfo)
1328
                );
1329
            }
1330
        }
1331
1332
        if (empty($recordInfo)) {
1333
            if (!$isAdminReport) {
1334
                if ($meetingInfo['status'] == 0) {
1335
                    $links[] = Display::url(
1336
                        Display::return_icon('delete.png', get_lang('Delete')),
1337
                        $this->deleteRecordUrl($meetingInfo)
1338
                    );
1339
                    $links[] = $linkVisibility;
1340
                }
1341
1342
                return $links;
1343
            } else {
1344
                $links[] = Display::url(
1345
                    Display::return_icon('course_home.png', get_lang('GoToCourse')),
1346
                    $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1347
                );
1348
1349
                return $links;
1350
            }
1351
        }
1352
1353
        if (!$isGlobal) {
1354
            $links[] = Display::url(
1355
                Display::return_icon('link.gif', get_lang('UrlMeetingToShare')),
1356
                $this->copyToRecordToLinkTool($meetingInfo)
1357
            );
1358
            $links[] = Display::url(
1359
                Display::return_icon('agenda.png', get_lang('AddToCalendar')),
1360
                $this->addToCalendarUrl($meetingInfo, $recordInfo)
1361
            );
1362
        }
1363
1364
        $hide = $this->plugin->get('disable_download_conference_link') === 'true' ? true : false;
1365
1366
        if ($hide == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1367
            if ($meetingInfo['has_video_m4v']) {
1368
                $links[] = Display::url(
1369
                    Display::return_icon('save.png', get_lang('DownloadFile')),
1370
                    $recordInfo['playbackFormatUrl'].'/capture.m4v',
1371
                    ['target' => '_blank']
1372
                );
1373
            } else {
1374
                $links[] = Display::url(
1375
                    Display::return_icon('save.png', get_lang('DownloadFile')),
1376
                    '#',
1377
                    [
1378
                        'id' => "btn-check-meeting-video-{$meetingInfo['id']}",
1379
                        'class' => 'check-meeting-video',
1380
                        'data-id' => $meetingInfo['id'],
1381
                    ]
1382
                );
1383
            }
1384
        }
1385
1386
1387
        if (!$isAdminReport) {
1388
            $links[] = Display::url(
1389
                Display::return_icon('delete.png', get_lang('Delete')),
1390
                $this->deleteRecordUrl($meetingInfo)
1391
            );
1392
            $links[] = $linkVisibility;
1393
        } else {
1394
            $links[] = Display::url(
1395
                Display::return_icon('course_home.png', get_lang('GoToCourse')),
1396
                $this->getListingUrl($meetingInfo['c_id'], $meetingInfo['session_id'], $meetingInfo['group_id'])
1397
            );
1398
        }
1399
1400
1401
        return $links;
1402
    }
1403
1404
    /**
1405
     * @param array $meeting
1406
     *
1407
     * @return string
1408
     */
1409
    public function unPublishUrl($meeting)
1410
    {
1411
        if (!isset($meeting['id'])) {
1412
            return null;
1413
        }
1414
1415
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1416
            ).'&action=unpublish&id='.$meeting['id'];
1417
    }
1418
1419
    /**
1420
     * @param array $meeting
1421
     *
1422
     * @return string
1423
     */
1424
    public function publishUrl($meeting)
1425
    {
1426
        if (!isset($meeting['id'])) {
1427
            return '';
1428
        }
1429
1430
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1431
            ).'&action=publish&id='.$meeting['id'];
1432
    }
1433
1434
    /**
1435
     * @param array $meeting
1436
     * @param array $recordInfo
1437
     *
1438
     * @return string
1439
     */
1440
    public function regenerateRecordUrl($meeting, $recordInfo)
1441
    {
1442
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1443
            return '';
1444
        }
1445
1446
        if (!isset($meeting['id'])) {
1447
            return '';
1448
        }
1449
1450
        if (empty($recordInfo) || (!empty($recordInfo['recordId']) && !isset($recordInfo['recordId']))) {
1451
            return '';
1452
        }
1453
1454
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().
1455
            '&action=regenerate_record&id='.$meeting['id'].'&record_id='.$recordInfo['recordId'];
1456
    }
1457
1458
    /**
1459
     * @param array $meeting
1460
     *
1461
     * @return string
1462
     */
1463
    public function regenerateRecordUrlFromMeeting($meeting)
1464
    {
1465
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1466
            return '';
1467
        }
1468
1469
        if (!isset($meeting['id'])) {
1470
            return '';
1471
        }
1472
1473
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().
1474
            '&action=regenerate_record&id='.$meeting['id'];
1475
    }
1476
1477
    /**
1478
     * @param array $meeting
1479
     *
1480
     * @return string
1481
     */
1482
    public function deleteRecordUrl($meeting)
1483
    {
1484
        if (!isset($meeting['id'])) {
1485
            return '';
1486
        }
1487
1488
        return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams(
1489
            ).'&action=delete_record&id='.$meeting['id'];
1490
    }
1491
1492
    /**
1493
     * @param array $meeting
1494
     *
1495
     * @return string
1496
     */
1497
    public function copyToRecordToLinkTool($meeting)
1498
    {
1499
        if (!isset($meeting['id'])) {
1500
            return '';
1501
        }
1502
1503
        return api_get_path(WEB_PLUGIN_PATH).
1504
            'bbb/listing.php?'.$this->getUrlParams().'&action=copy_record_to_link_tool&id='.$meeting['id'];
1505
    }
1506
1507
    /**
1508
     * Function disabled
1509
     */
1510
    public function publishMeeting($id)
1511
    {
1512
        //return BigBlueButtonBN::setPublishRecordings($id, 'true', $this->url, $this->salt);
1513
        if (empty($id)) {
1514
            return false;
1515
        }
1516
        $id = intval($id);
1517
        Database::update($this->table, array('visibility' => 1), array('id = ? ' => $id));
1518
1519
        return true;
1520
    }
1521
1522
    /**
1523
     * Function disabled
1524
     */
1525
    public function unpublishMeeting($id)
1526
    {
1527
        //return BigBlueButtonBN::setPublishRecordings($id, 'false', $this->url, $this->salt);
1528
        if (empty($id)) {
1529
            return false;
1530
        }
1531
        $id = intval($id);
1532
        Database::update($this->table, array('visibility' => 0), array('id = ?' => $id));
1533
1534
        return true;
1535
    }
1536
1537
    /**
1538
     * Get users online in the current course room.
1539
     *
1540
     * @return int The number of users currently connected to the videoconference
1541
     * @assert () > -1
1542
     */
1543
    public function getUsersOnlineInCurrentRoom()
1544
    {
1545
        $courseId = api_get_course_int_id();
1546
        $sessionId = api_get_session_id();
1547
1548
        $conditions = array(
1549
            'where' => array(
1550
                'c_id = ? AND session_id = ? AND status = 1 AND access_url = ?' => array(
1551
                    $courseId,
1552
                    $sessionId,
1553
                    $this->accessUrl,
1554
                ),
1555
            ),
1556
        );
1557
1558
        if ($this->hasGroupSupport()) {
1559
            $groupId = api_get_group_id();
1560
            $conditions = array(
1561
                'where' => array(
1562
                    'c_id = ? AND session_id = ? AND group_id = ? AND status = 1 AND access_url = ?' => array(
1563
                        $courseId,
1564
                        $sessionId,
1565
                        $groupId,
1566
                        $this->accessUrl,
1567
                    ),
1568
                ),
1569
            );
1570
        }
1571
1572
        if ($this->isGlobalConferencePerUserEnabled()) {
1573
            $conditions = array(
1574
                'where' => array(
1575
                    'user_id = ? AND status = 1 AND access_url = ?' => array(
1576
                        $this->userId,
1577
                        $this->accessUrl,
1578
                    ),
1579
                ),
1580
            );
1581
        }
1582
1583
        $meetingData = Database::select(
1584
            '*',
1585
            $this->table,
1586
            $conditions,
1587
            'first'
1588
        );
1589
1590
        if (empty($meetingData)) {
1591
            return 0;
1592
        }
1593
        $pass = $meetingData['moderator_pw'];
1594
        $info = $this->getMeetingInfo(array('meetingId' => $meetingData['remote_id'], 'password' => $pass));
1595
        if ($info === false) {
1596
            //checking with the remote_id didn't work, so just in case and
1597
            // to provide backwards support, check with the id
1598
            $params = array(
1599
                'meetingId' => $meetingData['id'],
1600
                //  -- REQUIRED - The unique id for the meeting
1601
                'password' => $pass
1602
                //  -- REQUIRED - The moderator password for the meeting
1603
            );
1604
            $info = $this->getMeetingInfo($params);
1605
        }
1606
1607
        if (!empty($info) && isset($info['participantCount'])) {
1608
            return $info['participantCount'];
1609
        }
1610
1611
        return 0;
1612
    }
1613
1614
    /**
1615
     * @param int    $id
1616
     * @param string $recordId
1617
     *
1618
     * @return bool
1619
     */
1620
    public function regenerateRecording($id, $recordId = '')
1621
    {
1622
        if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
1623
            return false;
1624
        }
1625
1626
        if (empty($id)) {
1627
            return false;
1628
        }
1629
1630
        $meetingData = Database::select(
1631
            '*',
1632
            $this->table,
1633
            array('where' => array('id = ?' => array($id))),
1634
            'first'
1635
        );
1636
1637
        Event::addEvent(
1638
            'bbb_regenerate_record',
1639
            'record_id',
1640
            (int) $recordId,
1641
            null,
1642
            api_get_user_id(),
1643
            api_get_course_int_id(),
1644
            api_get_session_id()
1645
        );
1646
1647
        // Check if there are recordings for this meeting
1648
        $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
1649
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] === 'noRecordings') {
1650
            // Regenerate the meeting id
1651
            if (!empty($meetingData['internal_meeting_id'])) {
1652
                return $this->api->generateRecording(['recordId' => $meetingData['internal_meeting_id']]);
1653
            }
1654
1655
            /*$pass = $this->getModMeetingPassword();
1656
            $info = $this->getMeetingInfo(['meetingId' => $meetingData['remote_id'], 'password' => $pass]);
1657
            if (!empty($info) && isset($info['internalMeetingID'])) {
1658
                return $this->api->generateRecording(['recordId' => $meetingData['internal_meeting_id']]);
1659
            }*/
1660
1661
            return false;
1662
        } else {
1663
            if (!empty($recordings['records'])) {
1664
                $recordExists = false;
1665
                foreach ($recordings['records'] as $record) {
1666
                    if ($recordId == $record['recordId']) {
1667
                        $recordExists = true;
1668
                        break;
1669
                    }
1670
                }
1671
1672
                if ($recordExists) {
1673
                    return $this->api->generateRecording(['recordId' => $recordId]);
1674
                }
1675
            }
1676
        }
1677
1678
        return false;
1679
    }
1680
1681
    /**
1682
     * Deletes a recording of a meeting
1683
     *
1684
     * @param int $id ID of the recording
1685
     *
1686
     * @return bool
1687
     *
1688
     * @assert () === false
1689
     * @todo Also delete links and agenda items created from this recording
1690
     */
1691
    public function deleteRecording($id)
1692
    {
1693
        if (empty($id)) {
1694
            return false;
1695
        }
1696
1697
        $meetingData = Database::select(
1698
            '*',
1699
            $this->table,
1700
            array('where' => array('id = ?' => array($id))),
1701
            'first'
1702
        );
1703
1704
        Event::addEvent(
1705
            'bbb_delete_record',
1706
            'meeting_id',
1707
            $id,
1708
            null,
1709
            api_get_user_id(),
1710
            api_get_course_int_id(),
1711
            api_get_session_id()
1712
        );
1713
1714
        $delete = false;
1715
        $recordings = [];
1716
        // Check if there are recordings for this meeting
1717
        if (!empty($meetingData['remote_id'])) {
1718
            Event::addEvent(
1719
                'bbb_delete_record',
1720
                'remote_id',
1721
                $meetingData['remote_id'],
1722
                null,
1723
                api_get_user_id(),
1724
                api_get_course_int_id(),
1725
                api_get_session_id()
1726
            );
1727
            $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
1728
        }
1729
        if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] == 'noRecordings') {
1730
            $delete = true;
1731
        } else {
1732
            if (!empty($recordings['records'])) {
1733
                $recordsToDelete = [];
1734
                foreach ($recordings['records'] as $record) {
1735
                    $recordsToDelete[] = $record['recordId'];
1736
                }
1737
                $delete = true;
1738
                if (!empty($recordsToDelete)) {
1739
                    $recordingParams = ['recordId' => implode(',', $recordsToDelete)];
1740
                    Event::addEvent(
1741
                        'bbb_delete_record',
1742
                        'record_id_list',
1743
                        implode(',', $recordsToDelete),
1744
                        null,
1745
                        api_get_user_id(),
1746
                        api_get_course_int_id(),
1747
                        api_get_session_id()
1748
                    );
1749
                    $result = $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
1750
                    if (!empty($result) && isset($result['deleted']) && $result['deleted'] === 'true') {
1751
                        $delete = true;
1752
                    }
1753
                }
1754
            }
1755
        }
1756
1757
        if ($delete) {
1758
            Database::delete(
1759
                'plugin_bbb_room',
1760
                array('meeting_id = ?' => array($id))
1761
            );
1762
1763
            Database::delete(
1764
                $this->table,
1765
                array('id = ?' => array($id))
1766
            );
1767
        }
1768
1769
        return $delete;
1770
    }
1771
1772
    /**
1773
     * Creates a link in the links tool from the given videoconference recording
1774
     *
1775
     * @param int $id ID of the item in the plugin_bbb_meeting table
1776
     * @param string Hash identifying the recording, as provided by the API
1777
     *
1778
     * @return mixed ID of the newly created link, or false on error
1779
     * @assert (null, null) === false
1780
     * @assert (1, null) === false
1781
     * @assert (null, 'abcdefabcdefabcdefabcdef') === false
1782
     */
1783
    public function copyRecordingToLinkTool($id)
1784
    {
1785
        if (empty($id)) {
1786
            return false;
1787
        }
1788
        //$records =  BigBlueButtonBN::getRecordingsUrl($id);
1789
        $meetingData = Database::select(
1790
            '*',
1791
            $this->table,
1792
            array('where' => array('id = ?' => array($id))),
1793
            'first'
1794
        );
1795
1796
        $records = $this->api->getRecordingsWithXmlResponseArray(
1797
            array('meetingId' => $meetingData['remote_id'])
1798
        );
1799
1800
        if (!empty($records)) {
1801
            if (isset($records['message']) && !empty($records['message'])) {
1802
                if ($records['messageKey'] == 'noRecordings') {
1803
                    $recordArray[] = $this->plugin->get_lang('NoRecording');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$recordArray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $recordArray = array(); before regardless.
Loading history...
1804
                } else {
1805
                    //$recordArray[] = $records['message'];
1806
                }
1807
1808
                return false;
1809
            } else {
1810
                $record = $records[0];
1811
                if (is_array($record) && isset($record['recordId'])) {
1812
                    $url = $record['playbackFormatUrl'];
1813
                    $link = new Link();
1814
                    $params['url'] = $url;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
1815
                    $params['title'] = $meetingData['meeting_name'];
1816
                    $id = $link->save($params);
1817
1818
                    return $id;
1819
                }
1820
            }
1821
        }
1822
1823
        return false;
1824
    }
1825
1826
    /**
1827
     * Checks if the video conference server is running.
1828
     * Function currently disabled (always returns 1)
1829
     * @return bool True if server is running, false otherwise
1830
     * @assert () === false
1831
     */
1832
    public function isServerRunning()
1833
    {
1834
        return true;
1835
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1836
    }
1837
1838
    /**
1839
     * Checks if the video conference plugin is properly configured
1840
     * @return bool True if plugin has a host and a salt, false otherwise
1841
     * @assert () === false
1842
     */
1843
    public function isServerConfigured()
1844
    {
1845
        $host = $this->plugin->get('host');
1846
1847
        if (empty($host)) {
1848
            return false;
1849
        }
1850
1851
        $salt = $this->plugin->get('salt');
1852
1853
        if (empty($salt)) {
1854
            return false;
1855
        }
1856
1857
        return true;
1858
        //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
1859
    }
1860
1861
    /**
1862
     * Get active session in the all platform
1863
     */
1864
    public function getActiveSessionsCount()
1865
    {
1866
        $meetingList = Database::select(
1867
            'count(id) as count',
1868
            $this->table,
1869
            array('where' => array('status = ? AND access_url = ?' => array(1, $this->accessUrl))),
1870
            'first'
1871
        );
1872
1873
        return $meetingList['count'];
1874
    }
1875
1876
    /**
1877
     * Get active session in the all platform
1878
     */
1879
    public function getActiveSessions()
1880
    {
1881
        $meetingList = Database::select(
1882
            '*',
1883
            $this->table,
1884
            array('where' => array('status = ? AND access_url = ?' => array(1, $this->accessUrl)))
1885
        );
1886
1887
        return $meetingList;
1888
    }
1889
1890
    /**
1891
     * @param string $url
1892
     */
1893
    public function redirectToBBB($url)
1894
    {
1895
        if (file_exists(__DIR__.'/../config.vm.php')) {
1896
            // Using VM
1897
            echo Display::url($this->plugin->get_lang('ClickToContinue'), $url);
1898
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1899
        } else {
1900
            // Classic
1901
            header("Location: $url");
1902
            exit;
1903
        }
1904
    }
1905
1906
    /**
1907
     * @return string
1908
     */
1909
    public function getConferenceUrl()
1910
    {
1911
        return api_get_path(WEB_PLUGIN_PATH).'bbb/start.php?launch=1&'.$this->getUrlParams();
1912
    }
1913
1914
    /**
1915
     * Get the meeting info from DB by its name
1916
     *
1917
     * @param string $name
1918
     *
1919
     * @return array
1920
     */
1921
    public function findMeetingByName($name)
1922
    {
1923
        $meetingData = Database::select(
1924
            '*',
1925
            'plugin_bbb_meeting',
1926
            array('where' => array('meeting_name = ? AND status = 1 ' => $name)),
1927
            'first'
1928
        );
1929
1930
        return $meetingData;
1931
    }
1932
1933
    /**
1934
     * Get the meeting info from DB by its name
1935
     *
1936
     * @param int $id
1937
     *
1938
     * @return array
1939
     */
1940
    public function getMeeting($id)
1941
    {
1942
        $meetingData = Database::select(
1943
            '*',
1944
            'plugin_bbb_meeting',
1945
            array('where' => array('id = ?' => $id)),
1946
            'first'
1947
        );
1948
1949
        return $meetingData;
1950
    }
1951
1952
    /**
1953
     * Get the meeting info.
1954
     *
1955
     * @param int $id
1956
     *
1957
     * @return array
1958
     */
1959
    public function getMeetingByRemoteId($id)
1960
    {
1961
        $meetingData = Database::select(
1962
            '*',
1963
            'plugin_bbb_meeting',
1964
            array('where' => array('remote_id = ?' => $id)),
1965
            'first'
1966
        );
1967
1968
        return $meetingData;
1969
    }
1970
1971
    /**
1972
     * @param int $meetingId
1973
     *
1974
     * @return array
1975
     */
1976
    public function findConnectedMeetingParticipants($meetingId)
1977
    {
1978
        $meetingData = Database::select(
1979
            '*',
1980
            'plugin_bbb_room',
1981
            array('where' => array('meeting_id = ? AND in_at IS NOT NULL' => $meetingId))
1982
        );
1983
        $participantIds = [];
1984
        $return = [];
1985
1986
        foreach ($meetingData as $participantInfo) {
1987
            if (in_array($participantInfo['participant_id'], $participantIds)) {
1988
                continue;
1989
            }
1990
1991
            $participantIds[] = $participantInfo['participant_id'];
1992
1993
            $return[] = [
1994
                'id' => $participantInfo['id'],
1995
                'meeting_id' => $participantInfo['meeting_id'],
1996
                'participant' => api_get_user_entity($participantInfo['participant_id']),
1997
                'in_at' => $participantInfo['in_at'],
1998
                'out_at' => $participantInfo['out_at'],
1999
            ];
2000
        }
2001
2002
        return $return;
2003
    }
2004
2005
    /**
2006
     * Check if the meeting has a capture.m4v video file. If exists then the has_video_m4v field is updated
2007
     *
2008
     * @param int $meetingId
2009
     *
2010
     * @return bool
2011
     */
2012
    public function checkDirectMeetingVideoUrl($meetingId)
2013
    {
2014
        $meetingInfo = Database::select(
2015
            '*',
2016
            'plugin_bbb_meeting',
2017
            [
2018
                'where' => ['id = ?' => intval($meetingId)],
2019
            ],
2020
            'first'
2021
        );
2022
2023
        if (!isset($meetingInfo['video_url'])) {
2024
            return false;
2025
        }
2026
2027
        $hasCapture = SocialManager::verifyUrl($meetingInfo['video_url'].'/capture.m4v');
2028
2029
        if ($hasCapture) {
2030
            return Database::update(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::update(...=> intval($meetingId))) also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
2031
                'plugin_bbb_meeting',
2032
                ['has_video_m4v' => true],
2033
                ['id = ?' => intval($meetingId)]
2034
            );
2035
        }
2036
2037
        return $hasCapture;
2038
    }
2039
}
2040